Untuk mempelajari pemrograman konkurensi Java, kita harus belajar tentang paket java.util.concurrent. Ada banyak kelas alat konkurensi yang sering kita gunakan di bawah paket ini, seperti: Reentrantlock, Countdownlatch, Cyclicbarrier, Semaphore, dll. Implementasi yang mendasari kelas ini semuanya bergantung pada kelas AbstractQueuedSynchronizer, yang menunjukkan pentingnya kelas ini. Jadi dalam seri Concurrency Java, saya pertama kali menganalisis kelas AbstractQueuedSynchronizer. Karena kelas ini lebih penting dan kodenya relatif panjang, untuk menganalisisnya sesengerinya, saya memutuskan untuk menggunakan empat artikel untuk memberikan pengantar yang relatif lengkap untuk kelas ini. Artikel ini adalah ringkasan pengantar untuk memberi pembaca pemahaman awal tentang kategori ini. Demi kesederhanaan narasi, beberapa tempat akan menggunakan AQ untuk mewakili kelas ini di masa depan.
1. Untuk apa kelas AbstractQueuedSynchronizer?
Saya percaya bahwa banyak pembaca telah menggunakan reentrantlock, tetapi mereka tidak tahu keberadaan abstractqueuedsynchronizer. Faktanya, Reentrantlock mengimplementasikan sinkronisasi kelas internal, yang mewarisi abstractqueuedsynchronizer. Semua implementasi mekanisme kunci bergantung pada kelas internal sinkronisasi. Dapat juga dikatakan bahwa implementasi Reentrantlock tergantung pada kelas AbstractQueuedSynchronizer. Demikian pula, kelas Countdownlatch, Cyclicbarrier, dan Semaphore juga menggunakan metode yang sama untuk mengimplementasikan kontrol kunci mereka sendiri. Dapat dilihat bahwa AbstractQueuedSynchronizer adalah landasan dari kelas -kelas ini. Jadi apa sebenarnya yang diimplementasikan di dalam AQS sehingga semua kelas ini bergantung padanya? Dapat dikatakan bahwa AQS menyediakan infrastruktur untuk kelas -kelas ini, yaitu, menyediakan kunci kata sandi. Setelah kelas -kelas ini memiliki kunci kata sandi, mereka dapat mengatur kata sandi kunci kata sandi sendiri. Selain itu, AQS juga menyediakan area antrian dan instruktur utas. Kita tahu bahwa utas seperti orang barbar primitif. Mereka tidak tahu bagaimana bersikap sopan. Mereka hanya akan bergegas, jadi Anda harus mengajarkannya selangkah demi selangkah, menceritakannya ketika perlu mengantri, di mana harus mengantri, apa yang harus dilakukan sebelum mengantri, dan apa yang harus dilakukan setelah antrian. Semua pekerjaan pendidikan ini diselesaikan oleh AQS untuk Anda. Benang yang dididik darinya telah menjadi sangat beradab dan sopan, dan bukan lagi orang barbar primitif. Jadi di masa depan, kita hanya perlu berurusan dengan utas beradab ini. Tidak pernah memiliki terlalu banyak kontak dengan utas asli!
2. Mengapa AbstractQueuedSynchronizer menyediakan kunci kata sandi?
// simpul kepala dari head simpul volatil transien privat Sinkronisasi; // simpul ekor dari antrian sinkronisasi node volatil transien swasta; // status int volatil swasta; // Dapatkan sinkronisasi status sinkronisasi yang dilindungi int getState () {return state;} // Set Sinkronisasi Sinkronisasi, Perluasan Final StateAnze (Int NewState) {State = Newstate; {return unsafe.comppeeandswapint (this, stateoffset, harapkan, perbarui);}Kode di atas mencantumkan semua variabel anggota AQS. Anda dapat melihat bahwa hanya ada tiga variabel anggota AQS, yaitu referensi simpul head antrian sinkronisasi, referensi simpul tail antrian sinkronisasi, dan keadaan sinkronisasi. Perhatikan bahwa ketiga variabel anggota dimodifikasi dengan kata kunci yang mudah menguap, yang memastikan bahwa beberapa utas memodifikasi itu terlihat memori. Inti dari seluruh kelas adalah keadaan sinkronisasi ini. Anda dapat melihat bahwa status sinkronisasi sebenarnya adalah variabel tipe-intr. Anda dapat menganggap status sinkronisasi ini sebagai kunci kata sandi, dan itu juga kunci kata sandi yang terkunci dari ruangan. Nilai spesifik negara setara dengan kata sandi yang mengendalikan pembukaan dan penutupan kunci kata sandi. Tentu saja, kata sandi kunci ini ditentukan oleh setiap subkelas. Misalnya, di reentrantlock, status sama dengan 0 berarti bahwa kunci terbuka, status lebih besar dari 0 berarti kunci terkunci, dan dalam semaphore, status lebih dari 0 berarti bahwa kunci terbuka, dan status sama dengan 0 berarti kunci terkunci.
3. Bagaimana area antrian dari AbstractQueuedSynchronizer diimplementasikan?
Sebenarnya ada dua area antrian di dalam abstractqueuedsynchronizer, satu adalah antrian sinkron dan yang lainnya adalah antrian bersyarat. Seperti yang dapat dilihat dari gambar di atas, hanya ada satu antrian sinkronisasi, sementara bisa ada beberapa kondisi antrian. Node dari referensi penahan antrian sinkron masing -masing ke node depan dan belakang, sedangkan node dari antrian bersyarat hanya memiliki satu referensi ke simpul penerus. Pada gambar, T mewakili utas. Setiap node berisi utas. Setelah utas gagal memperoleh kunci, pertama -tama memasuki antrian sinkronisasi untuk mengantri. Jika Anda ingin memasukkan antrian bersyarat, utas harus menahan kunci. Selanjutnya, mari kita lihat struktur setiap node dalam antrian.
// node dari antrian sinkron adalah node kelas akhir statis {static final node shared = new node (); // Utas saat ini menahan kunci dalam mode bersama node final node eksklusif = null; // Utas saat ini menahan kunci dalam mode eksklusif statis final int cancel = 1; // Node saat ini telah membatalkan sinyal int static static static = -1; // utas simpul penerus perlu menjalankan kondisi int statis final = -2; // Node saat ini antri dalam antrian bersyarat statis int propagate = -3; // simpul selanjutnya dapat secara langsung memperoleh intratil volatile int waitstatus; // menunjukkan keadaan menunggu node volatile node saat ini; // menunjukkan node depan dalam simpul volatile antrian sinkronisasi selanjutnya; // menunjukkan simpul penerus dalam utas antrian sinkronisasi utas volatile; // utas yang dipegang oleh simpul saat ini mengacu pada node Nextwaaiter; // menunjukkan simpul penerus dalam antrian bersyarat // adalah keadaan simpul saat ini di final mode bersama Boolean isShared () {return nextwaaiter == dibagikan; } // Kembalikan node maju dari node node saat ini predecessor () melempar nullpointerException {node p = prev; if (p == null) {lempar nullPointerException baru (); } else {return p; }} // konstruktor 1 node () {} // konstruktor 2, konstruktor ini digunakan oleh node default (utas utas, mode node) {// Perhatikan bahwa mode holding ditetapkan ke nextwaaiter this.nextwaaiter = mode; this.thread = thread; } // Konstruktor 3, hanya node (utas utas, int waitstatus) yang digunakan dalam kondisi antrian {this.waitstatus = waitstatus; this.thread = thread; }}Node mewakili sebuah node dalam antrian sinkronisasi dan antrian bersyarat. Ini adalah kelas dalam AbstractQueuedsynchronizer. Node memiliki banyak atribut, seperti mode holding, status tunggu, pra-urutan dan penerus dalam antrian sinkron, dan referensi penerus dalam antrian bersyarat, dll. Antrian sinkronisasi dan antrian kondisi dapat dianggap sebagai area antrian, masing-masing node dianggap sebagai kursi di area antrian, dan utas dianggap sebagai tanya. Ketika para tamu pertama kali datang, mereka akan mengetuk pintu untuk melihat apakah kunci dibuka. Jika kunci tidak dibuka, mereka akan pergi ke area antrian untuk mengumpulkan plat nomor, menyatakan dengan cara apa mereka ingin memegang kunci, dan akhirnya antrian di ujung antrian.
4 Bagaimana cara memahami mode eksklusif dan mode berbagi?
Seperti yang disebutkan sebelumnya, setiap tamu akan menerima piring nomor sebelum mengantri dan menyatakan bahwa ia ingin memiliki kunci. Cara memiliki kunci dibagi menjadi mode eksklusif dan mode berbagi. Jadi, bagaimana Anda memahami mode eksklusif dan mode berbagi? Saya benar -benar tidak dapat menemukan analogi yang baik. Anda dapat memikirkan toilet umum. Orang dengan mode eksklusif lebih mendominasi. Saya tidak masuk. Ketika saya masuk, saya tidak membiarkan orang lain masuk. Saya menempati seluruh toilet sendirian. Orang dalam mode berbagi tidak begitu khusus. Ketika mereka menemukan bahwa toilet sudah dapat digunakan, itu tidak diperhitungkan jika masuk dengan sendirinya. Mereka juga harus dengan antusias bertanya kepada orang -orang di belakang apakah mereka keberatan menggunakannya bersama. Jika orang -orang di belakang tidak keberatan menggunakannya bersama, tidak perlu mengantri. Semua orang akan pergi bersama. Tentu saja, jika orang -orang di belakang pikiran mereka, mereka harus tetap dalam antrian dan terus mengantri.
5 Bagaimana cara memahami keadaan menunggu sebuah node?
Kami juga melihat bahwa setiap node memiliki keadaan menunggu, yang dibagi menjadi empat negara: dibatalkan, sinyal, kondisi, dan merambat. Keadaan menunggu ini dapat dianggap sebagai tanda yang tergantung di sebelah kursi, mengidentifikasi keadaan menunggu orang di kursi saat ini. Status merek ini tidak hanya dapat dimodifikasi sendiri, tetapi orang lain juga dapat memodifikasinya. Misalnya, ketika utas ini telah merencanakan untuk menyerah selama antrian, itu akan menetapkan tanda di kursi untuk dibatalkan, sehingga orang lain dapat menghapusnya dari antrian jika mereka melihatnya. Situasi lain adalah bahwa ketika utas akan tertidur di kursi, itu takut akan tidur berlebihan, jadi itu akan mengubah tanda di posisi depan menjadi memberi sinyal, karena semua orang akan kembali ke kursi mereka sebelum meninggalkan antrian untuk melihatnya. Jika ia melihat bahwa status pada tanda adalah sinyal, itu akan membangunkan orang berikutnya. Hanya dengan memastikan bahwa merek di posisi depan adalah sinyal, utas saat ini akan tidur nyenyak. Status kondisi menunjukkan bahwa utasnya antri dalam antrian bersyarat. Status perambatan mengingatkan utas berikutnya untuk memperoleh kunci secara langsung. Status ini hanya digunakan dalam mode bersama, dan akan dibahas nanti ketika berbicara tentang mode bersama secara terpisah.
6. Operasi apa yang akan dilakukan ketika sebuah node memasuki antrian sinkronisasi?
// Operasi node enqueue, kembali ke simpul node sebelumnya node enq (simpul node akhir) {untuk (;;) {// Dapatkan referensi ke simpul ekor dari simpul antrian sinkronisasi t = tail; // Jika simpul ekor kosong, itu berarti bahwa antrian sinkronisasi belum diinisialisasi jika (t == null) {// menginisialisasi antrian sinkronisasi jika (compareANDSethead (node baru ())) {tail = head; }} else {// 1. Arahkan ke simpul ekor saat ini node.prev = t; // 2. Atur simpul saat ini ke simpul ekor if (compareEndsettail (t, node)) {// 3. Arahkan penerus simpul ekor lama ke simpul ekor baru t.next = node; // Satu -satunya keluar dari loop returns t; }}}}Perhatikan bahwa operasi enqueue menggunakan loop dead. Hanya ketika simpul berhasil ditambahkan ke ekor antrian sinkronisasi akan dikembalikan. Hasilnya adalah simpul ekor asli dari antrian sinkronisasi. Gambar berikut menunjukkan seluruh proses operasi.
Pembaca perlu memperhatikan urutan menambahkan node ekor, yang dibagi menjadi tiga langkah: menunjuk ke node ekor, CAS mengubah node ekor, dan menunjuk penerus simpul ekor lama ke node saat ini. Dalam lingkungan yang bersamaan, ketiga langkah ini mungkin tidak dijamin akan diselesaikan. Oleh karena itu, dalam operasi membersihkan semua node yang dibatalkan dalam antrian sinkronisasi, untuk menemukan node dalam keadaan non-kansel, itu tidak dilalui dari depan ke belakang tetapi dari belakang ke depan. Juga, ketika setiap node memasuki antrian, keadaan menunggu adalah 0. Hanya ketika utas simpul berikutnya perlu ditangguhkan akan keadaan menunggu dari simpul sebelumnya diubah menjadi sinyal.
Catatan: Semua analisis di atas didasarkan pada JDK1.7, dan akan ada perbedaan antara versi yang berbeda, pembaca perlu memperhatikan.
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.