Dalam artikel sebelumnya "Java Concurrency Series [1] ----- AbstractQueuedSynchronizer Analisis Kode Sumber", kami memperkenalkan beberapa konsep dasar AbstractQueuedSynchronizer, terutama berbicara tentang bagaimana area antrian AQS diimplementasikan, apa mode eksklusif dan mode yang dibagikan, dan bagaimana memahami keadaan menunggu node. Memahami dan menguasai konten ini adalah kunci untuk membaca kode sumber AQS berikutnya, jadi disarankan agar pembaca membaca artikel saya sebelumnya terlebih dahulu dan kemudian melihat kembali artikel ini untuk memahaminya. Dalam artikel ini, kami akan memperkenalkan bagaimana node memasukkan antrian antrian sinkronisasi dalam mode eksklusif dan operasi apa yang akan dilakukan sebelum meninggalkan antrian sinkronisasi. AQS menyediakan tiga cara untuk mendapatkan kunci dalam mode eksklusif dan mode bersama: akuisisi interupsi utas non-responsif, akuisisi interupsi utas respons, dan menetapkan akuisisi batas waktu. Langkah -langkah keseluruhan dari ketiga metode ini kira -kira sama, dengan hanya beberapa bagian yang berbeda, jadi jika Anda memahami satu metode dan melihat implementasi metode lain, Anda akan serupa. Dalam artikel ini, saya akan fokus pada metode akuisisi untuk tidak menanggapi interupsi utas, dan dua metode lainnya juga akan berbicara tentang ketidakkonsistenan.
1. Bagaimana cara mendapatkan kunci dengan interupsi utas yang tidak responsif?
// tidak menanggapi akuisisi metode interupsi (mode eksklusif) public final void Acquire (int arg) {if (! TRYACQUIRE (arg) && AcquireQueued (addWaiter (node.exclusive), arg)) {selfInterrupt (); }}Meskipun kode di atas terlihat sederhana, ia melakukan 4 langkah yang ditunjukkan pada gambar di bawah ini secara berurutan. Di bawah ini kami akan mendemonstrasikan dan menganalisis langkah demi langkah.
Langkah 1 :! TRYACQUIRE (ARG)
// Cobalah untuk memperoleh kunci (mode eksklusif) boolean tryacquire yang dilindungi (int arg) {lempar new unsportedoperationException ();}Pada saat ini, seseorang datang dan dia mencoba mengetuk pintu terlebih dahulu. Jika dia menemukan bahwa pintu tidak terkunci (TREACQUIRE (ARG) = Benar), dia akan masuk secara langsung. Jika Anda menemukan bahwa pintu terkunci (TRYACQUIRE (arg) = false), maka lakukan langkah berikutnya. Metode Tryacquire ini menentukan kapan kunci terbuka dan kapan kunci ditutup. Metode ini harus ditimpa oleh subkelas dan menulis ulang logika penilaian di dalamnya.
Langkah 2: AddWaiter (Node.Exclusive)
// Bungkus utas saat ini ke dalam node dan tambahkan ke ekor dari sinkronisasi antrian private node addwaaiter (mode node) {// Tentukan mode yang menahan simpul kunci node = node baru (thread.currentThread (), mode); // Dapatkan referensi simpul ekor dari simpul antrian sinkronisasi pred = ekor; // Jika simpul ekor tidak kosong, itu berarti bahwa antrian sinkronisasi sudah memiliki node if (pred! = Null) {// 1. Arahkan ke simpul ekor saat ini node.prev = pred; // 2. Atur simpul saat ini ke simpul ekor if (compareEndsettail (pred, node)) {// 3. Arahkan penerus simpul ekor lama ke simpul ekor baru pred.next = node; return node; }} // Sebaliknya, itu berarti bahwa antrian sinkronisasi belum diinisialisasi enq (node); return node;} // node enqueue private node enq (node node akhir) {for (;;) {// Dapatkan referensi 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; mengembalikan t; }}}}Eksekusi pada langkah ini menunjukkan bahwa pertama kali akuisisi kunci gagal, sehingga orang tersebut akan mendapatkan kartu angka untuk dirinya sendiri dan memasuki area antrian untuk mengantri. Saat menerima kartu nomor, dia akan menyatakan bagaimana dia ingin menempati ruangan (mode eksklusif atau mode berbagi). Perhatikan bahwa dia tidak duduk dan beristirahat saat ini (gantung dirinya).
Langkah 3: AcquireQueued (Addwaaiter (Node.Exclusive), Arg)
// Mengakuisisi kunci dengan cara yang tidak terputus (mode eksklusif) final boolean diakuisisi (node node akhir, int arg) {boolean gagal = true; coba {boolean interrupted = false; untuk (;;) {// Dapatkan referensi dari simpul sebelumnya dari simpul node node p = node.predecessor (); // Jika node saat ini adalah simpul pertama dari antrian sinkronisasi, cobalah untuk memperoleh kunci if (p == head && tryacquire (arg)) {// atur node yang diberikan sebagai simpul head sethead (node); // Untuk membantu pengumpulan sampah, hapus penerus simpul kepala sebelumnya p.next = null; // Tetapkan keadaan akuisisi yang berhasil gagal = false; // Kembalikan keadaan terputus, seluruh loop dieksekusi di sini, pengembalian keluar terputus; } // Kalau tidak, itu berarti status kunci masih belum tersedia. Pada saat ini, tentukan apakah utas saat ini dapat ditangguhkan // Jika hasilnya benar, utas saat ini dapat ditangguhkan, jika tidak, loop akan berlanjut, selama periode ini, utas tidak akan menanggapi interupsi jika (harus diterjemahkan ke interupsi (p, node) && parkandCheckInterrupt ()) {interrupted = true; }}} akhirnya {// pastikan untuk membatalkan akuisisi jika (gagal) {cancelacquire (node); }}} // menilai apakah itu dapat menangguhkan node private static boolean yang saat ini harus diparkirfailedacquire (node pred, simpul simpul) {// Dapatkan keadaan menunggu dari node depan int ws = pred.waitstatus; // Jika keadaan node depan adalah sinyal, itu berarti bahwa node depan akan membangunkan simpul saat ini, sehingga simpul saat ini dapat dengan aman menangguhkan jika (ws == node.signal) {return true; } if (ws> 0) {// operasi berikut adalah untuk membersihkan semua node maju yang dibatalkan dalam antrian sinkronisasi do {node.prev = pred = pred.prev; } while (pred.waitstatus> 0); pred.next = node; } else {// Untuk tujuan ini, itu berarti bahwa keadaan simpul depan bukan sinyal, dan kemungkinan sama dengan 0. Dengan cara ini, simpul ke depan tidak akan membangunkan simpul saat ini.//so simpul saat ini harus memastikan bahwa keadaan simpul ke depan adalah sinyal untuk menggantungnya dengan aman compareAndsetWaitStatus (pred, ws, node.sign.sign. } return false;} // Tangguhkan utas saat ini private boolean parkandcheckinterrupt () {locksupport.park (ini); return thread.interrupted ();}Setelah mendapatkan tanda nomor, ia akan segera mengimplementasikan metode ini. Ketika sebuah node memasuki area antrian untuk pertama kalinya, ada dua situasi. Salah satunya adalah dia menemukan bahwa orang di depannya telah meninggalkan kursinya dan memasuki ruangan, jadi dia tidak akan duduk dan beristirahat, dan akan mengetuk pintu lagi untuk melihat apakah anak itu selesai. Jika orang di dalam baru saja selesai, dia akan bergegas tanpa memanggil dirinya sendiri. Kalau tidak, dia harus mempertimbangkan untuk duduk dan beristirahat sebentar, tetapi dia masih khawatir. Bagaimana jika tidak ada yang mengingatkannya setelah dia duduk dan tertidur? Dia meninggalkan catatan kecil di kursi pria di depan sehingga orang yang keluar dari dalam bisa membangunkannya setelah melihat catatan itu. Situasi lain adalah bahwa ketika dia memasuki area antrian dan menemukan bahwa ada beberapa orang yang antri di depannya, dia bisa duduk untuk sementara waktu, tetapi sebelum itu, dia masih akan meninggalkan catatan di kursi orang di depan (dia sudah tertidur pada saat ini) sehingga orang itu bisa membangunkannya sebelum pergi. Ketika semuanya selesai, dia tidur nyenyak. Perhatikan bahwa kita melihat bahwa seluruh loop hanya memiliki satu pintu keluar, yaitu, itu hanya bisa keluar setelah utas berhasil memperoleh kunci. Sebelum kunci diperoleh, selalu digantung di metode parkandcheckinterrupt () dari loop untuk. Setelah utas dibangunkan, ia juga terus mengeksekusi loop untuk tempat ini.
Langkah 4: Selfintrupt ()
// utas saat ini akan mengganggu dirinya sendiri void statis mandiri () {thread.currentThread (). Interrupt (); }Karena seluruh utas di atas telah digantung di metode parkandcheckinterrupt () dari for loop, itu tidak menanggapi segala bentuk interupsi utas sebelum berhasil diperoleh. Hanya ketika utas berhasil memperoleh kunci dan keluar dari loop untuk memeriksa apakah seseorang meminta untuk mengganggu utas selama periode ini. Jika demikian, hubungi metode selfinterrupt () untuk menggantung sendiri.
2. Bagaimana cara mendapatkan kunci dalam menanggapi interupsi utas?
// Mendapatkan kunci dalam mode interruptible (mode eksklusif) membatalkan doacquireerTeribly (int arg) melempar interruptedException {// membungkus utas saat ini ke dalam node dan menambahkannya ke simpul antrian sinkronisasi node akhir = addwaaiter (node.exclusive); boolean gagal = true; coba {untuk (;;) {// mendapatkan simpul node sebelumnya p = node.predecessor (); // Jika P adalah simpul kepala, maka utas saat ini mencoba untuk mendapatkan kunci lagi jika (p == head && tryacquire (arg)) {sethead (node); p.next = null; // bantu GC gagal = false; // pengembalian kembali setelah kunci berhasil diperoleh; } // Jika kondisinya terpenuhi, utas saat ini akan ditangguhkan. Pada saat ini, interupsi ditanggapi dan pengecualian dilemparkan jika (harus diparkir darifailedacquire (p, node) && parkandcheckinterrupt ()) {// Jika utasnya dibangunkan, pengecualian akan dilemparkan jika permintaan interupsi ditemukan, kegagalan akan dilemparkan. Lempar interupsi baru (); }}} akhirnya {if (gagal) {cancelacquire (node); }}}Metode interupsi utas respons dan metode interupsi utas non-responsif kira-kira sama dalam proses mendapatkan kunci. Satu -satunya perbedaan adalah bahwa setelah utas bangun dari metode ParkandCheckInterrupt, itu akan memeriksa apakah utas terganggu. Jika demikian, itu akan melempar pengecualian ExcepedException. Alih -alih menanggapi kunci akuisisi interupsi utas, itu hanya menetapkan status interupsi setelah menerima permintaan interupsi, dan tidak akan segera mengakhiri metode saat ini untuk memperoleh kunci. Itu tidak akan memutuskan apakah akan menggantung dirinya berdasarkan keadaan interupsi setelah node berhasil memperoleh kunci.
3. Bagaimana cara mengatur waktu batas waktu untuk mendapatkan kunci?
// Mengakuisisi kunci dengan batas waktu terbatas (mode eksklusif) boolean doacquirenanos (int arg, nanostimeout panjang) melempar interruptedException {// mendapatkan waktu sistem saat ini yang lama terakhir kali = System.nanoTime (); // Membungkus utas saat ini ke dalam sebuah node dan menambahkannya ke simpul final antrian sinkronisasi = addWaiter (node.exclusive); boolean gagal = true; coba {untuk (;;) {// mendapatkan simpul node sebelumnya p = node.predecessor (); // Jika simpul sebelumnya adalah simpul kepala, maka utas saat ini mencoba untuk memperoleh kunci lagi jika (p == head && tryacquire (arg)) {// Perbarui head node sethead (node); p.next = null; gagal = false; Kembali Benar; } // Setelah waktu batas waktu digunakan, keluar dari loop secara langsung jika (nanoSticeOut <= 0) {return false; } //If the timeout time is greater than the spin time, then after judging that the thread can be suspended, the thread will be suspended for a period of time if (shouldParkAfterFailedAcquire(p, node) && nanosTimeout > spinForTimeoutThreshold) { //Have the current thread for a period of time, and then wake up by itself LockSupport.parkNanos(this, nanosTimeout); } // Dapatkan waktu saat ini dari sistem Long Now = System.nanoTime (); // Waktu batas waktu dikurangi dari interval waktu dari kunci akuisisi nanostimeout - = sekarang - terakhir; // Perbarui terakhir lagi lagi terakhir = sekarang; // Pengecualian dilemparkan ketika permintaan interupsi diterima selama akuisisi kunci IF (thread.interrupted ()) {lempar New InterruptedException (); }}} akhirnya {if (gagal) {cancelacquire (node); }}}Mengatur akuisisi waktu batas waktu akan pertama -tama akan memperoleh kunci. Setelah akuisisi pertama kali gagal, itu akan didasarkan pada situasi. Jika waktu batas waktu yang masuk lebih besar dari waktu putaran, utas akan ditangguhkan untuk jangka waktu tertentu, jika tidak maka akan berputar. Setelah setiap kali kunci diperoleh, waktu batas waktu akan dikurangi dari waktu yang dibutuhkan untuk memperoleh kunci. Sampai batas waktu kurang dari 0, itu berarti waktu batas waktu telah digunakan. Kemudian operasi untuk memperoleh kunci akan diakhiri dan bendera kegagalan akuisisi akan dikembalikan. Perhatikan bahwa selama proses memperoleh kunci dengan waktu batas waktu, Anda dapat menanggapi permintaan interupsi utas.
4. Bagaimana benang melepaskan kunci dan meninggalkan antrian sinkronisasi?
// Rilis Operasi Kunci (Mode Eksklusif) Rilis Boolean Akhir Publik (int arg) {// Putar kunci kata sandi untuk melihat apakah dapat membuka kunci IF (tryrelease (arg)) {// Dapatkan simpul head node h = head; // Jika simpul kepala tidak kosong dan keadaan menunggu tidak sama dengan 0, bangun simpul penerus if (h! = Null && h.waitstatus! = 0) {// bangun simpul penerus node unparkuccessor (h); } return true; } return false;} // Bangun simpul smaster swaster void unparkuccessor (node node) {// Dapatkan status menunggu dari node int ws = node.waitstatus; // perbarui status menunggu ke 0 if (ws <0) {compareANDSetwaitstatus (node, ws, 0); } // Dapatkan simpul selanjutnya dari simpul simpul yang diberikan s = node.next; // simpul penerus kosong atau keadaan menunggu dibatalkan jika (s == null || s.waitstatus> 0) {s = null; // Selesaikan simpul pertama yang tidak dibatalkan dari antrian traversal ke belakang untuk (node t = tail; t! = Null && t! = Node; t = t.prev) {if (t.waitstatus <= 0) {s = t; }}} // Bangunkan simpul pertama setelah simpul tertentu yang bukan keadaan batal jika (s! = Null) {locksupport.unpark (s.thread); }}Setelah utas menahan kunci ke dalam ruangan, ia akan melakukan bisnisnya sendiri. Setelah pekerjaan selesai, itu akan melepaskan kunci dan meninggalkan ruangan. Kunci kata sandi dapat dibuka melalui metode tryrelease. Kita tahu bahwa metode tryrelease perlu ditimpa oleh subkelas. Aturan implementasi subkelas yang berbeda berbeda, yang berarti bahwa kata sandi yang ditetapkan oleh subkelas yang berbeda berbeda. Misalnya, di Reentrantlock, setiap kali orang di ruangan itu memanggil metode tryrelease, negara akan dikurangi dengan 1, sampai keadaan dikurangi menjadi 0, kunci kata sandi akan dibuka. Pikirkan apakah proses ini sepertinya kita terus -menerus memutar roda kunci kata sandi, dan jumlah roda dikurangi dengan 1 setiap kali kita memutarnya. Countdownlatch sedikit mirip dengan yang ini, kecuali bahwa itu bukan hanya mengubah satu orang, tetapi itu akan mengubah satu orang, memusatkan kekuatan semua orang untuk membuka kunci. Setelah benang meninggalkan ruangan, ia akan menemukan kursi aslinya, yaitu, temukan simpul kepala. Lihat apakah ada yang meninggalkan catatan kecil untuk itu di kursi. Jika ada, ia akan tahu bahwa seseorang tertidur dan perlu memintanya untuk membantu membangunkannya, dan kemudian akan membangunkan utas itu. Jika tidak, itu berarti bahwa tidak ada yang menunggu dalam antrian sinkronisasi untuk saat ini, dan tidak ada yang membutuhkannya untuk bangun, sehingga dapat meninggalkan ketenangan pikiran. Proses di atas adalah proses melepaskan kunci dalam mode eksklusif.
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.