Dalam [high concurrency Java II] fondasi multi-threading, kami awalnya telah menyebutkan operasi sinkronisasi utas dasar. Apa yang ingin kami sebutkan kali ini adalah alat kontrol sinkronisasi dalam paket bersamaan.
1. Penggunaan berbagai alat kontrol sinkronisasi
1.1 Reentrantlock
Reentrantlock terasa seperti versi yang disinkronkan. Fitur yang disinkronkan adalah mudah digunakan dan semuanya diserahkan kepada JVM untuk diproses, tetapi fungsinya relatif lemah. Sebelum JDK1.5, kinerja Reentrantlock lebih baik daripada disinkronkan. Karena optimalisasi JVM, kinerja keduanya dalam versi JDK saat ini sebanding. Jika ini adalah implementasi sederhana, jangan sengaja menggunakan reentrantlock.
Dibandingkan dengan sinkronisasi, reentrantlock lebih kaya secara fungsional, dan memiliki karakteristik reentrant, interruptible, waktu terbatas, dan penguncian yang adil.
Pertama, mari kita gunakan contoh untuk menggambarkan penggunaan awal reentrantlock:
tes paket; import java.util.concurrent.locks.reentrantlock; tes kelas publik mengimplementasikan runnable {public static reentrantlock lock = baru reentrantlock (); Int statis publik i = 0; @Override public void run () {for (int j = 0; j <10000000; j ++) {lock.lock (); coba {i ++; } akhirnya {lock.unlock (); }}} public static void main (string [] args) melempar interruptedException {test test = new test (); Utas T1 = utas baru (tes); Thread t2 = utas baru (tes); t1.start (); t2.start (); t1.join (); t2.join (); System.out.println (i); }}Ada dua utas yang melakukan operasi ++ pada i. Untuk memastikan keamanan utas, reentrantlock digunakan. Dari penggunaan, kita dapat melihat bahwa dibandingkan dengan sinkronisasi, reentrantlock sedikit lebih rumit. Karena operasi buka kunci harus dilakukan di akhirnya, jika akhirnya tidak terkunci, ada kemungkinan bahwa kode memiliki pengecualian dan kunci tidak dirilis, dan disinkronkan dilepaskan oleh JVM.
Jadi apa karakteristik reentrantlock yang sangat baik?
1.1.1 masuk kembali
Utas tunggal dapat berulang kali dimasukkan, tetapi harus berulang kali keluar
lock.lock (); lock.lock (); coba {i ++; } akhirnya {lock.unlock (); lock.unlock ();}Karena Reentrantlock adalah kunci reentrant, Anda bisa mendapatkan kunci yang sama berulang kali, yang memiliki konter akuisisi terkait kunci. Jika utas yang memiliki kunci mendapatkan kunci lagi, konter akuisisi meningkat sebesar 1, dan kunci perlu dilepaskan dua kali untuk mendapatkan rilis nyata (Reentrant Lock). Ini meniru semantik yang disinkronkan; Jika utas memasuki blok yang disinkronkan yang dilindungi oleh monitor yang sudah dimiliki oleh utas, utas dibiarkan berlanjut. Ketika utas keluar dari blok disinkronkan kedua (atau berikutnya), kunci tidak dilepaskan. Kunci hanya dilepaskan ketika utas keluar dari blok tersinkronisasi pertama yang dilindungi oleh monitor yang masuk.
Anak kelas publik memperluas ayah mengimplementasikan runnable {final static child child = new child (); // Untuk memastikan bahwa mengunci void statis publik yang unik (string [] args) {for (int i = 0; i <50; i ++) {utas baru (anak) .start (); }} public disinkronkan void dosomething () {System.out.println ("1Child.dosomething ()"); doanotherthing (); // hubungi metode sinkronisasi lain di kelas Anda sendiri} private disinkronkan void doanotherthing () {super.dosomething (); // Panggil metode sinkronisasi dari kelas induk System.out.println ("3child.doanotherthing ()"); } @Override public void run () {child.dosomething (); }} kelas ayah {public disinkronkan void dosomething () {System.out.println ("2Father.Dosomething ()"); }}Kita dapat melihat bahwa utas memasuki metode yang disinkronkan yang berbeda dan tidak akan melepaskan kunci yang diperoleh sebelumnya. Jadi outputnya masih berurutan. Oleh karena itu disinkronkan juga merupakan kunci reentrant
Keluaran:
1Child.Dosomething ()
2Father.Dosomething ()
3Child.Doanotherthing ()
1Child.Dosomething ()
2Father.Dosomething ()
3Child.Doanotherthing ()
1Child.Dosomething ()
2Father.Dosomething ()
3Child.Doanotherthing ()
...
1.1.2. Interrupt
Tidak seperti disinkronkan, reentrantlock responsif terhadap interupsi. Pandangan Pengetahuan Terkait Terkait [Java 2] Dasar Multithreading Tinggi
Lock.lock biasa () tidak dapat menanggapi interupsi, lock.lockinterruptible () dapat merespons interupsi.
Kami mensimulasikan adegan kebuntuan dan kemudian menggunakan interupsi untuk menangani kebuntuan
Tes Paket; Impor Java.lang.Management.ManagementFactory; impor java.lang.management.threadinfo; impor java.lang.management.threadmxbean; impor tes kelas publik. reentrantlock static public lock2 = baru reentrantlock (); kunci int; tes publik (int lock) {this.lock = lock; } @Override public void run () {coba {if (lock == 1) {lock1.lockinterricture (); coba {thread.sleep (500); } catch (exception e) {// todo: handle exception} lock2.lockinterricture (); } else {lock2.lockinterricture (); coba {thread.sleep (500); } catch (exception e) {// todo: handle exception} lock1.lockinterricture (); }} catch (exception e) {// todo: handle exception} akhirnya {if (lock1.isheldbyCurrentThread ()) {lock1.unlock (); } if (lock2.isheldbyCurrentThread ()) {lock2.unlock (); } System.out.println (thread.currentThread (). GetId () + ": thread exit"); }} public static void main (string [] args) melempar interruptedException {test t1 = tes baru (1); Tes T2 = tes baru (2); Thread thread1 = utas baru (t1); Thread thread2 = utas baru (T2); thread1.start (); thread2.start (); Thread.sleep (1000); //Deadlockchecker.check (); } kelas statis deadlockchecker {private final static threadmxbean mbean = manajemenFactory .getThreadMxBean (); final static runnable deadlockchecker = new runnable () {@Override public void run () {// TODO Metode yang dihasilkan otomatis Stub while (true) {long [] deadlockedthreadids = mbean.finddeadlockedthreads (); if (deadlockedThreadIds! = null) {threadInfo [] threadInfos = mbean.getThreadInfo (deadlockedthreadids); untuk (thread t: thread.getAllStackTraces (). keyset ()) {for (int i = 0; i <threadInfos.length; i ++) {if (t.getId () == threadInfos [i] .getThreadId ()) {t.Interrupt (); }}}}} coba {thread.sleep (5000); } catch (Exception e) {// todo: menangani pengecualian}}}}}; public static void check () {thread t = Thread baru (deadlockchecker); t.setdaemon (true); t.start (); }}}Kode di atas dapat menyebabkan kebuntuan, utas 1 mendapat lock1, thread 2 mendapat lock2, dan kemudian satu sama lain ingin mendapatkan kunci satu sama lain.
Kami menggunakan JStack untuk melihat situasi setelah menjalankan kode di atas
Kebuntuan memang ditemukan.
The Deadlockchecker.check (); Metode digunakan untuk mendeteksi kebuntuan dan kemudian mengganggu benang kebuntuan. Setelah gangguan, utas keluar secara normal.
1.1.3. Terbatas waktu
Jika batas waktu tidak dapat memperoleh kunci, itu akan mengembalikan false dan tidak akan menunggu secara permanen untuk membentuk kunci mati.
Gunakan lock.trylock (waktu lama, unit timeUnit) untuk mengimplementasikan kunci waktu yang mudah, dengan parameter menjadi waktu dan unit.
Izinkan saya memberi Anda contoh untuk menggambarkan bahwa waktunya dapat dibatasi:
Tes Paket; Impor java.util.concurrent.timeunit; impor java.util.concurrent.locks.reentrantlock; tes kelas publik mengimplementasikan runnable {public static reentrantlock lock = baru reentrantlock (); @Override public void run () {coba {if (lock.trylock (5, timeunit.seconds)) {thread.sleep (6000); } else {System.out.println ("Get Lock Failure"); }} catch (exception e) {} akhirnya {if (lock.isheldbyCurrentThread ()) {lock.unlock (); }}} public static void main (string [] args) {test t = new test (); Thread t1 = utas baru (t); Utas T2 = utas baru (t); t1.start (); t2.start (); }}Gunakan dua utas untuk bersaing untuk mengunci. Ketika utas memperoleh kunci, tidur 6 detik, dan masing -masing utas hanya mencoba untuk mendapatkan kunci selama 5 detik.
Jadi harus ada utas yang tidak bisa mendapatkan kunci. Jika Anda tidak bisa mendapatkannya, Anda akan langsung keluar.
Keluaran:
Dapatkan kunci gagal
1.1.4. Kunci yang adil
Bagaimana menggunakan:
Publik Reentrantlock (Boolean Fair)
Reentrantlock statis publik fairlock = baru reentrantlock (true);
Kunci secara umum tidak adil. Tidak mungkin bahwa utas yang datang pertama dapat mendapatkan kunci terlebih dahulu, tetapi utas yang datang nanti akan mendapatkan kunci nanti. Kunci yang tidak adil dapat menyebabkan kelaparan.
Kunci yang adil berarti bahwa kunci ini dapat memastikan bahwa utas lebih dulu dan mendapatkan kunci terlebih dahulu. Meskipun kunci yang adil tidak akan menyebabkan kelaparan, kinerja kunci yang adil akan jauh lebih buruk daripada kunci non-fair.
1.2 Kondisi
Hubungan antara kondisi dan reentrantlock mirip dengan disinkronkan dan objek.Wait ()/sinyal ()
Metode AWAIT () akan membuat utas saat ini menunggu dan melepaskan kunci saat ini. Ketika sinyal () digunakan di utas lain atau metode SignalAll (), utas akan mendapatkan kembali kunci dan terus mengeksekusi. Atau ketika utas terganggu, Anda juga dapat melompat keluar dari menunggu. Ini sangat mirip dengan metode objek.Wait ().
Metode AwaitUninterruptible () pada dasarnya sama dengan metode AWAIT (), tetapi tidak akan menunggu respons interupsi selama proses. Metode singal () digunakan untuk membangunkan utas menunggu. Metode relatif singalall () akan membangunkan semua utas menunggu. Ini sangat mirip dengan metode objct.notify ().
Saya tidak akan memperkenalkannya secara detail di sini. Izinkan saya memberi Anda contoh untuk diilustrasikan:
Tes Paket; Impor java.util.concurrent.locks.condition; impor java.util.concurrent.locks.reentrantlock; tes kelas publik mengimplementasikan runnable {public static reentrantlock lock = baru reentrantlock (); kondisi statis publik kondisi = lock.newcondition (); @Override public void run () {coba {lock.lock (); condition.Await (); System.out.println ("Thread Is Wading On"); } catch (Exception e) {E.PrintStackTrace (); } akhirnya {lock.unlock (); }} public static void main (string [] args) melempar interruptedException {test t = new test (); Thread thread = utas baru (t); thread.start (); Thread.sleep (2000); lock.lock (); condition.signal (); lock.unlock (); }}Contoh di atas sangat sederhana. Biarkan utas menunggu dan biarkan utas utama membangunkannya. condition.Await ()/sinyal hanya dapat digunakan setelah mendapatkan kunci.
1.3.semaphore
Untuk kunci, ini saling eksklusif. Itu berarti bahwa selama saya mendapatkan kunci, tidak ada yang bisa mendapatkannya lagi.
Untuk semaphore, ini memungkinkan beberapa utas untuk memasukkan bagian kritis secara bersamaan. Ini dapat dianggap sebagai kunci bersama, tetapi batas bersama terbatas. Setelah batas digunakan, utas lain yang belum memperoleh batas masih akan memblokir di luar area kritis. Saat jumlahnya 1, setara dengan mengunci
Inilah contohnya:
Tes Paket; Impor java.util.concurrent.executorservice; impor java.util.concurrent.executors; impor java.util.concurrent.semaphore; tes kelas publik mengimplementasikan runnable {final semaphore semaphore = baru semaphore (5); @Override public void run () {coba {semaphore.acquire (); Thread.sleep (2000); System.out.println (thread.currentThread (). GetId () + "Done"); } catch (Exception e) {E.PrintStackTrace (); } akhirnya {semaphore.release (); }} public static void main (string [] args) melempar interruptedException {executorService executorService = executors.newfixedThreadpool (20); tes akhir t = tes baru (); untuk (int i = 0; i <20; i ++) {ExecutorService.submit (t); }}}Ada kumpulan utas dengan 20 utas, dan setiap utas masuk ke lisensi Semaphore. Hanya ada 5 lisensi untuk semaphore. Setelah berjalan, Anda dapat melihat bahwa 5 adalah output dalam batch, batch adalah output.
Tentu saja, satu utas juga dapat mengajukan beberapa lisensi sekaligus
public void accquire (int izin) melempar interupredException
1.4 ReadWritelock
ReadWritelock adalah kunci yang membedakan fungsi. Membaca dan menulis adalah dua fungsi yang berbeda: membaca membaca tidak saling eksklusif, baca-write saling eksklusif, dan tulis-menulis saling eksklusif.
Desain ini meningkatkan konkurensi dan memastikan keamanan data.
Bagaimana menggunakan:
private static reentrantreadwritelock readwritelock = baru reentrantreadwritelock ();
private static lock readlock = readwritelock.readlock ();
private static lock writelock = readwritelock.writelock ();
Untuk contoh terperinci, Anda dapat melihat implementasi Java dari masalah produser dan konsumen dan masalah pembaca dan penulis, dan saya tidak akan memperluasnya di sini.
1.5 Countdownlatch
Skenario khas untuk penghitung waktu mundur adalah peluncuran roket. Sebelum roket diluncurkan, untuk memastikan bahwa semuanya sangat mudah, inspeksi berbagai peralatan dan instrumen sering dilakukan. Mesin hanya bisa menyala setelah semua inspeksi selesai. Skenario ini sangat cocok untuk Countdownlatch. Itu dapat membuat utas pengapian menunggu semua utas cek untuk diselesaikan sebelum menjalankannya
Bagaimana menggunakan:
Static Final CountdownLatch end = Countdownlatch baru (10);
end.countdown ();
end.aWait ();
Diagram skematik:
Contoh sederhana:
Tes Paket; Impor java.util.concurrent.countdownlatch; import java.util.concurrent.executorservice; import java.util.concurrent.executors; uji kelas publik mengimplementasikan runnable {static final countdownlatch countdownlatch = countdownlatch baru (10); tes akhir statis t = tes baru (); @Override public void run () {coba {thread.sleep (2000); System.out.println ("Lengkap"); Countdownlatch.countdown (); } catch (Exception e) {E.PrintStackTrace (); }} public static void main (string [] args) melempar interruptedException {executorService executorService = executors.newfixedThreadpool (10); untuk (int i = 0; i <10; i ++) {ExecutorService.execute (t); } countDownlatch.Await (); System.out.println ("end"); ExecutorService.shutdown (); }}Utas utama harus menunggu semua 10 utas untuk dieksekusi sebelum mengeluarkan "akhir".
1.6 Cyclicbarrier
Mirip dengan CountdownLatch, itu juga menunggu beberapa utas untuk diselesaikan sebelum menjalankannya. Perbedaan dengan Countdownlatch adalah bahwa penghitung ini dapat digunakan berulang kali. Sebagai contoh, misalkan kita mengatur penghitung ke 10. Kemudian setelah mengumpulkan batch pertama 10 utas, konter akan kembali ke nol, dan kemudian mengumpulkan batch berikutnya dari 10 utas
Bagaimana menggunakan:
Publik Cyclicbarrier (Pihak Int, Runnable Barrieraction)
Barrieraction adalah tindakan yang akan dilakukan sistem ketika penghitung dihitung sekali.
menunggu()
Diagram skematik:
Inilah contohnya:
tes paket; import java.util.concurrent.cyclicbarrier; uji kelas publik mengimplementasikan runnable {private string prajurit; siklik cyclicbarrier final swasta; Tes Publik (String Soldier, Cyclicbarrier Cyclic) {this.soldier = prajurit; this.cyclic = siklik; } @Override public void run () {coba {// tunggu semua tentara tiba cyclic.Await (); dowork (); // tunggu semua tentara untuk menyelesaikan pekerjaan mereka cyclic.Await (); } catch (Exception e) {// TODO Auto-Entoerated Catch Block E.PrintStackTrace (); }} private void dowork () {// TODO Metode yang dihasilkan secara otomatis coba {thread.sleep (3000); } catch (exception e) {// todo: handle exception} system.out.println (prajurit + ": done"); } public static class barrierrun mengimplementasikan runnable {boolean flag; int n; Barrierrun publik (bendera boolean, int n) {super (); this.flag = bendera; this.n = n; } @Override public void run () {if (flag) {System.out.println (n + "Tugas penyelesaian"); } else {System.out.println (n + "Setel penyelesaian"); bendera = true; }}} public static void main (string [] args) {final int n = 10; Thread [] utas = utas baru [n]; bendera boolean = false; Cyclicbarrier Barrier = Cyclicbarrier baru (N, barrierrun baru (bendera, n)); System.out.println ("Set"); untuk (int i = 0; i <n; i ++) {System.out.println (i+"Report"); utas [i] = utas baru (tes baru ("prajurit" + i, penghalang)); utas [i] .start (); }}}Hasil Cetak:
mengumpulkan
0 Laporan
1 Laporan
2 Laporan
3 Laporan
4 Laporan
5 Laporan
6 Laporan
7 Laporan
8 Laporan
9 Laporan
10 set SOLDIER Lengkap 5: Selesai
Soldier 7: Selesai
Soldier 8: Selesai
Soldier 3: Selesai
Soldier 4: Selesai
Tentara 1: Selesai
Soldier 6: Selesai
Soldier 2: Selesai
Soldier 0: Selesai
Soldier 9: Selesai
10 tugas selesai
1.7 Locksupport
Memberikan benang pemblokiran primitif
Mirip dengan SUSPEND
Locksupport.park ();
Locksupport.unpark (T1);
Dibandingkan dengan penangguhan, tidak mudah untuk menyebabkan pembekuan benang.
Gagasan Locksupport agak mirip dengan semaphore. Ini memiliki lisensi internal. Ini menghilangkan lisensi ini saat diparkir dan berlaku untuk lisensi ini saat membuka tanda. Oleh karena itu, jika unpark sebelum parkir, pembekuan benang tidak akan terjadi.
Kode berikut adalah kode sampel suspend di [Java 2] multi-threading foundation [High Concurrency Java 2]. Kebuntuan terjadi saat menggunakan penangguhan.
uji paket; import java.util.concurrent.locks.locksupport; tes kelas publik {objek statis u = objek baru (); TestsuspendThread T1 = testsuspendThread baru ("T1"); TestsuspendThread statis T2 = testsuspendThread baru ("T2"); tes kelas statis publicSuspendThread memperluas utas {public testsuspendThread (nama string) {setName (name); } @Override public void run () {disinkronkan (u) {System.out.println ("di" + getName ()); //Thread.currentThread (). Suspend (); Locksupport.park (); }}} public static void main (string [] args) melempar interruptedException {t1.start (); Thread.sleep (100); t2.start (); // t1.resume (); // t2.resume (); Locksupport.unpark (T1); Locksupport.unpark (T2); t1.join (); t2.join (); }}Namun, menggunakan Locksupport tidak akan menyebabkan kebuntuan.
Selain itu
Park () dapat menanggapi interupsi, tetapi tidak melempar pengecualian. Hasil dari respons interupsi adalah bahwa pengembalian fungsi taman () dapat memperoleh bendera interupsi dari utas.
Ada banyak tempat di JDK yang menggunakan Park, tentu saja, implementasi Locksupport juga diimplementasikan menggunakan uncafe.park ().
public static void park () {
uncafe.park (false, 0l);
}
1.8 Implementasi Reentrantlock
Mari kita perkenalkan implementasi Reentrantlock. Implementasi reentrantlock terutama terdiri dari tiga bagian:
Kelas induk Reentrantlock akan memiliki variabel keadaan untuk mewakili keadaan sinkron.
/*** Keadaan sinkronisasi. */ status int private volatile;
Atur status untuk memperoleh kunci melalui operasi CAS. Jika diatur ke 1, pemegang kunci diberikan ke utas saat ini
final void lock () {if (compareEndsetState (0, 1)) setExclusiveOwnerThread (thread.currentThread ()); lain memperoleh (1); }Jika kunci tidak berhasil, aplikasi akan dibuat
public final void acquire (int arg) {if (! tryacquire (arg) && AcquireQueued (addwaaiter (node.exclusive), arg)) selfinterrupt (); }Pertama, coba tryacquire setelah melamar, karena utas lain mungkin telah merilis kunci.
Jika Anda masih belum mendaftar untuk kunci, tambahkan pelayan, yang berarti menambahkan diri Anda ke antrian menunggu
private node addwaaiter (mode node) {node node = node baru (thread.currentThread (), mode); // coba jalur cepat enq; cadangan ke enq penuh pada simpul kegagalan pred = tail; if (pred! = null) {node.prev = pred; if (compareANDSettail (pred, node)) {pred.next = node; return node; }} enq (node); return node; }Selama periode ini, akan ada banyak upaya untuk mengajukan kunci, dan jika Anda masih tidak dapat melamar, Anda akan digantung.
private boolean parkandcheckinterrupt () {locksupport.park (ini); return thread.interrupted (); }Demikian pula, jika kunci dilepaskan dan kemudian unpark tidak dibahas secara rinci di sini.
2. Wadah Bersamaan dan Analisis Kode Sumber Khas
2.1 ConcurrenthashMap
Kita tahu bahwa hashmap bukan wadah yang aman. Cara termudah untuk membuat hashmap-aman adalah menggunakan
Collections.synchronizedMap, ini adalah pembungkus untuk hashmap
peta statis publik m = collections.synchronizedMap (new HashMap ());
Demikian pula, untuk daftar, set juga menyediakan metode serupa.
Namun, metode ini hanya cocok untuk kasus di mana jumlah konkurensi relatif kecil.
Mari kita lihat implementasi SynchronizedMap
peta akhir pribadi <k, v> m; // Backing Map Final Object Mutex; // objek untuk menyinkronkan SynchronizedMap (peta <k, v> m) {if (m == null) melempar nullPointerException baru (); this.m = m; mutex = ini; } SinkronisasiMap (peta <k, v> m, mutex objek) {this.m = m; this.mutex = mutex; } public int size() { synchronized (mutex) { return m.size();} } public boolean isEmpty() { synchronized (mutex) {return m.isEmpty();} } public boolean containsKey(Object key) { synchronized (mutex) {return m.containsKey(key);} } public boolean containsValue(Object value) { synchronized (mutex) {return m.containsValue(value);} } public V get(Object key) { synchronized (mutex) {return m.get(key);} } public V put(K key, V value) { synchronized (mutex) {return m.put(key, value);} } public V remove(Object key) { synchronized (mutex) {return m.remove (key);}} public void putall (peta <? Extends k ,? Extends v> peta) {disinkronkan (mutex) {m.putall (peta);}} public void clear () {disinkronkan (mutex) {m.clear ();}}Itu membungkus hashmap di dalam dan kemudian menyinkronkan setiap operasi hashmap.
Karena setiap metode memperoleh kunci yang sama (mutex), ini berarti bahwa operasi seperti put dan hapus saling eksklusif, sangat mengurangi jumlah konkurensi.
Mari kita lihat bagaimana concurrenthashmap diimplementasikan
public v put (key k, nilai v) {segmen <k, v> s; if (value == null) lempar nullpointerException baru (); int hash = hash (kunci); int j = (hash >>> SegmentShift) & Segmentmask; if ((s = (segmen <k, v>) unsafe.getObject // nonvolatile; periksa ulang (segmen, (j << sshift) + sbase)) == null) // dalam memastikan s = memastikan bagian (j); return s.put (kunci, hash, nilai, false); }Ada segmen segmen di dalam concurrenthashmap, yang membagi hashmap besar menjadi beberapa segmen (hashmap kecil), dan kemudian hash data pada setiap segmen. Dengan cara ini, operasi hash dari beberapa utas pada segmen yang berbeda harus aman, jadi Anda hanya perlu menyinkronkan utas pada segmen yang sama, yang mewujudkan pemisahan kunci dan sangat meningkatkan konkurensi.
Ini akan lebih merepotkan saat menggunakan concurrenthashmap.Size karena perlu menghitung jumlah data dari setiap segmen. Saat ini, Anda perlu menambahkan kunci ke setiap segmen dan kemudian melakukan statistik data. Ini adalah kerugian kecil setelah memisahkan kunci, tetapi metode ukuran tidak boleh dipanggil pada frekuensi tinggi.
Dalam hal implementasi, kami tidak menggunakan sinkronisasi dan kunci. Lok tetapi trylock sebanyak mungkin. Pada saat yang sama, kami juga telah membuat beberapa optimasi dalam implementasi HashMap. Saya tidak akan menyebutkannya di sini.
2.2 BlockingQueue
BlockingQueue bukan wadah berkinerja tinggi. Tapi ini adalah wadah yang sangat bagus untuk berbagi data. Ini adalah implementasi khas produsen dan konsumen.
Diagram skematik:
Untuk detailnya, Anda dapat memeriksa implementasi Java dari masalah produser dan konsumen dan masalah pembaca dan penulis.