Artikel ini membantu semua orang memahami threadlocal dengan menggunakan kasus optimasi SimpleDateFormat yang tidak aman di lingkungan bersamaan.
Baru -baru ini, saya memilah proyek perusahaan dan menemukan bahwa ada banyak hal buruk yang ditulis, seperti berikut:
Kelas Publik DateUtil {private final static SimpleDateFormat sdfyhm = new SimpleDateFormat ("yyyymmdd"); Tanggal statis yang disinkronkan publik parseymdhms (sumber string) {coba {return sdfyhm.parse (sumber); } catch (parseException e) {e.printstacktrace (); mengembalikan tanggal baru (); }}} Pertama, mari kita analisis:
Fungsi parseymdhms () pada titik ini menggunakan modifikasi yang disinkronkan, yang berarti bahwa operasi tersebut tidak aman, sehingga perlu disinkronkan. Thread-Unsafe hanya bisa menjadi metode parse () dari SimpledateFormat. Periksa kode sumbernya. Ada variabel global di SimpleDateFormat.
Kalender Kalender yang Dilindungi; Tanggal Parse () {Calendar.Clear (); ... // Lakukan beberapa operasi untuk menetapkan tanggal kalender dan kalender lainnya. GetTime (); // Dapatkan waktu kalender}Operasi Clear () akan menyebabkan benang tidak aman.
Selain itu, menggunakan kata kunci yang disinkronkan memiliki dampak besar pada kinerja, terutama ketika multi-threading, setiap kali metode parseymDHMS disebut, penilaian sinkronisasi akan dibuat, dan sinkronisasi itu sendiri sangat mahal, jadi ini adalah solusi yang tidak masuk akal.
Metode perbaikan
Thread Insecure disebabkan oleh penggunaan variabel bersama oleh beberapa utas, jadi di sini kami menggunakan threadlocal <MenementedateFormat> untuk membuat variabel salin untuk setiap utas secara terpisah. Pertama berikan kode, dan kemudian analisis penyebab masalah ini untuk menyelesaikan masalah.
/** * Kelas Alat Tanggal (Threadlocal digunakan untuk mendapatkan SimpleDateFormat, dan metode lain dapat disalin secara langsung oleh Common-lang) * @Author niu li * @Date 2016/11/19 */kelas publik dateutil {private static peta <string, threadLocal <MenStredateFormat>> SDFMAP = hashmap baru, string new hashmap <; Private Static Logger Logger = LoggerFactory.getLogger (Dateutil.class); string statis akhir publik mdhmss = "mmddhhmmsssss"; string statis akhir publik ymdhms = "yyymmddhhmmss"; String statis akhir publik ymdhms_ = "yyyy-mm-dd hh: mm: ss"; string statis akhir publik ymd = "yyyymmdd"; string statis akhir publik ymd_ = "yyyy-mm-dd"; string statis akhir publik hms = "hhmmss"; / *** Dapatkan instance SDF dari utas yang sesuai berdasarkan tombol di Peta* @param Pattern Key di peta* @return ini contoh*/ private static SimpleDateFormat getSdf (pola string akhir) {threadlocal <MenSementateFormat> sdfThread = sdfmap.get (pola); if (sdfThread == null) {// Periksa ganda untuk mencegah SDFMAP dari menempatkan nilainya ke beberapa kali, dan alasan singleton kunci ganda sama dengan disinkronkan (dateutil.class) {sdfthread = sdfmap.get (pola); if (sdfthread == null) {logger.debug ("Letakkan SDF baru pola" + pola + "ke peta"); sdfThread = threadLocal baru <MenementErateFormat> () {@Override Protected SimpleDateFormat InitialValue () {Logger.Debug ("Thread:" + Thread.CurrentThread () + "Init Pattern:" + Pattern); return new SimpleDateFormat (pola); }}; sdfmap.put (pola, sdfthread); }}} return sdfThread.get (); } / *** Parsing tanggal sesuai dengan pola yang ditentukan* @param Tanggal tanggal yang akan diuraikan* @param tanggal pola Tentukan format* @Return Parsed Date Instance* / Public Static Tanggal Parsedate (Tanggal String, Pola String) {if (Tanggal == NULL) {Throw IllegalArgumentException baru ("Tanggal tidak boleh ada. } coba {return getsdf (pola) .parse (date); } catch (parseException e) {e.printstacktrace (); Logger.Error ("Format parsed tidak mendukung:"+pola); } return null; } / *** Tanggal format sesuai dengan pola yang ditentukan* @param Tanggal tanggal yang akan diformat* Pola @param Tentukan format* @Return Parsed format* / format string statis publik (tanggal tanggal, pola string) {if (date == null) {throw IllegalArgumentException ("Tanggal HARUS BULU } else {return getsdf (pola) .format (date); }}}tes
Jalankan satu di utas utama dan dua lainnya di utas anak, keduanya menggunakan pola yang sama
public static void main (string [] args) {dateutil.formatdate (new date (), mdhmss); utas baru (()-> {dateutil.FormatDate (tanggal baru (), mdhmss);}). start (); utas baru (()-> {dateutil.FormatDate (tanggal baru (), mdhmss);}). start (); }Analisis log
Masukkan SDF baru dari pola mmddhhmmsssss ke MapThread: thread [main, 5, utama] pola init: mmddhhmmssssthread: thread [thread-0,5, utama] pola init: mmddhhmmssssthread: thread [thread-1, utama] pola: mmddhmmssssthread: thread [thread-1, Main] Pola init: mmddhmmsssssthread: thread [thread-1,5, Main] Pola init: mmddhm
menganalisa
Dapat dilihat bahwa SDFMAP dimasukkan sekali, sementara SimpledateFormat adalah baru tiga kali karena ada tiga utas dalam kode. Jadi mengapa ini?
Untuk setiap utas utas, ada referensi variabel global ke threadlocal.threadlocalmap Threadlocals. Ada threadlocal.threadlocalmap yang menampung threadlocal dan nilai yang sesuai. Satu gambar lebih baik dari seribu kata. Diagram struktur adalah sebagai berikut:
Jadi untuk SDFMAP, diagram struktur akan diubah
1. Pertama menjalankan dateutil.FormatDate (tanggal baru (), mdhmss);
// Pertama kali saya mengeksekusi dateUtil.FormatDate (tanggal baru (), MDHMS) Analisis Private Static SimpleDateFormat GetSDF (Pola String Akhir) {ThreadLocal <MenementedateFormat> sdfThread = sdfmap.get (pola); // SdfThread yang diperoleh adalah null, masukkan pernyataan if if (sdfthread == null) {disinkronkan (dateutil.class) {sdfThread = sdfmap.get (pola); // sdfthread masih nol, masukkan pernyataan if if (sdfthread == null) {// cetak logger.debug ("Letakkan SDF baru pola" + pola + "ke peta"); // Buat instance threadlocal dan override metode initialValue sdfThread = new ThreadLocal <MenementedateFormat> () {@Override Dilindungi SimpleDateFormat InitialValue () {Logger.Debug ("Thread:" + Thread.currentThread () + "Init Pattern:" + Pattern); return new SimpleDateFormat (pola); }}; // diatur ke SDFMAP SDFMAP.PUT (POLA, SDFTHREAD); }}} return sdfThread.get (); } Pada saat ini, seseorang mungkin bertanya, metode set threadlocal tidak dipanggil di sini, jadi bagaimana Anda mengatur nilainya untuk dimasukkan?
Ini membutuhkan implementasi sdfthread.get ():
public t get () {thread t = thread.currentThread (); ThreadLocalMap Map = getMap (t); if (peta! = null) {threadlocalmap.entry e = map.getEntry (this); if (e! = null) {@suppresswarnings ("Uncecked") t result = (t) e.value; hasil pengembalian; }} return setInitialValue (); }Dengan kata lain, ketika nilainya tidak ada, metode setInitialValue () akan dipanggil, yang akan memanggil metode initialValue (), yang merupakan metode yang kita ikuti.
Pencetakan log yang sesuai.
Masukkan SDF baru dari pola mmddhhmmsssss ke mapThread: thread [main, 5, utama] pola init: mmddhhmmsssss
2. Jalankan Dateutil.FormatDate (Tanggal Baru (), MDHMS) Pada utas anak untuk kedua kalinya;
// Jalankan `dateUtil.FormatDate (tanggal baru (), mdhmss);` private static SimpleDateFormat getSdf (pola string akhir) {threadLocal <MenSementedateFormat> sdfThread = sdfmap.get (pola); // SdfThread yang diperoleh di sini bukan null, lewati jika blok if (sdfthread == null) {disinkronkan (dateutil.class) {sdfthread = sdfmap.get (pola); if (sdfthread == null) {logger.debug ("Letakkan SDF baru pola" + pola + "ke peta"); sdfThread = threadLocal baru <MenementErateFormat> () {@Override Protected SimpleDateFormat InitialValue () {Logger.Debug ("Thread:" + Thread.CurrentThread () + "Init Pattern:" + Pattern); return new SimpleDateFormat (pola); }}; sdfmap.put (pola, sdfthread); }}} // hubungi sdfthread.get () langsung untuk mengembalikan sdfthread.get (); }Analisis sdfThread.get ()
// Jalankan `dateutil.FormatDate (tanggal baru (), mdhmss);` public t get () {thread t = thread.currentThread (); // Dapatkan thread anak saat ini saat ini peta utas saat ini = getMap (t); // Peta yang diperoleh di utas anak adalah nol, lewati jika blok if (peta! = Null) {threadlocalmap.entry e = map.getEntry (ini); if (e! = null) {@suppresswarnings ("Uncecked") t result = (t) e.value; hasil pengembalian; }} // Jalankan inisialisasi secara langsung, yaitu, panggil metode InitialValue () yang kita timpa pengembalian setInitialValue (); }Log yang sesuai:
Thread [Thread-1,5, Main] Pola Init: MMDDHHMMSSSSS
Meringkaskan
Dalam skenario manakah yang lebih cocok untuk menggunakan ThreadLocal? Seseorang memberikan jawaban yang cukup bagus di Stackoverflow.
Kapan dan bagaimana saya harus menggunakan variabel threadlocal?
Salah satu penggunaan yang mungkin (dan umum) adalah ketika Anda memiliki beberapa objek yang tidak aman, tetapi Anda ingin menghindari sinkronisasi akses ke objek itu (saya melihat Anda, SimpledateFormat). Sebaliknya, berikan setiap instance dari objek sendiri.
Kode referensi:
https://github.com/nl101531/util-demo di bawah javaweb
Referensi:
Pelajari Java Threadlocal dengan cara yang mudah dipahami
Masalah dan solusi keamanan utas SimpleDateFormat
Di atas adalah semua konten artikel ini. Saya berharap ini akan membantu untuk pembelajaran semua orang dan saya harap semua orang akan lebih mendukung wulin.com.