Kata pengantar
Siapa pun yang akrab dengan pemrograman bersamaan Java tahu bahwa aturan yang terjadi sebelum (HB) dalam JMM (model memori Java), yang mendefinisikan ketertiban dan visibilitas operasi multi-threaded Java, mencegah dampak penataan ulang kompiler pada hasil program.
Ada aturan "ketika sebelumnya" dalam bahasa Java. Ini adalah hubungan urutan parsial antara dua operasi yang ditentukan dalam model memori Java. Jika operasi A terjadi pertama dalam operasi B, itu berarti bahwa sebelum operasi B terjadi, dampak operasi A dapat diamati dengan operasi B. "Pengaruh" termasuk memodifikasi nilai variabel bersama dalam memori, mengirim pesan, metode panggilan, dll., Yang pada dasarnya tidak ada hubungannya dengan urutan kejadian dalam waktu. Prinsip ini sangat penting. Ini adalah dasar utama untuk menilai apakah ada persaingan dalam data dan apakah utasnya aman.
Menurut pernyataan resmi:
Ketika suatu variabel dibaca oleh banyak utas dan ditulis oleh setidaknya satu utas, jika tidak ada hubungan HB antara operasi baca dan tulis, masalah lomba data akan muncul.
Untuk memastikan bahwa utas yang beroperasi B melihat hasil operasi A (terlepas dari apakah A dan B berada di utas yang sama), prinsip HB harus dipenuhi antara A dan B, dan jika tidak, itu dapat menyebabkan pemesanan ulang.
Ketika hubungan HB hilang, masalah pemesanan ulang dapat terjadi.
Apa aturan untuk HB?
Setiap orang sangat akrab dengan ini. Sebagian besar buku dan artikel akan diperkenalkan. Mari kita tinjau secara singkat di sini:
Di antara mereka, saya telah menebus aturan pengiriman, yang sangat penting. Cara menggunakan aturan pengiriman dengan mahir adalah kunci untuk mencapai sinkronisasi.
Kemudian, jelaskan HB dari perspektif lain: ketika operasi HB beroperasi B, maka hasil operasi Operasi A pada variabel bersama terlihat oleh Operasi B.
Pada saat yang sama, jika operasi B HB beroperasi C, maka hasil operasi operasi A pada variabel bersama terlihat untuk operasi B.
Prinsip pencapaian visibilitas adalah protokol cache dan penghalang memori. Visibilitas dicapai melalui protokol koherensi cache dan hambatan memori.
Bagaimana cara mencapai sinkronisasi?
Dalam buku Doug Lea "Java Concurrency in Practice", deskripsi berikut adalah:
Buku ini menyebutkan: Dengan menggabungkan beberapa aturan HB, visibilitas variabel yang dilindungi tidak terkunci dapat dicapai.
Tetapi karena teknik ini sensitif terhadap urutan pernyataan, itu rentan terhadap kesalahan.
Selanjutnya, penulis akan menunjukkan cara menyinkronkan variabel melalui aturan yang mudah menguap dan aturan pesanan program.
Mari kita memiliki contoh yang sudah dikenal:
class threadPrintDemo {static int num = 0; Bendera boolean volatil statis = false; public static void main (string [] args) {thread t1 = thread baru (() -> {for (; 100> num;) {if (! flag && (num == 0 || ++ num % 2 == 0)) {System.out.println (num); flag = true;}}}); Thread t2 = utas baru (() -> {for (; 100> num;) {if (flag && (++ num % 2! = 0)) {System.out.println (num); flag = false;}}}); t1.start (); t2.start (); }}Tujuan kode ini adalah untuk mencetak angka 0 - 100 antara dua utas.
Siswa yang akrab dengan pemrograman bersamaan harus mengatakan bahwa variabel NUM ini tidak menggunakan volatile, dan akan ada masalah visibilitas, yaitu, utas T1 telah memperbarui NUM, dan utas T2 tidak dapat melihatnya.
Haha, penulis berpikir begitu di awal, tetapi baru -baru ini melalui mempelajari aturan HB, saya menemukan bahwa tidak masalah untuk menghapus modifikasi volatile num.
Mari kita analisis dan poster itu menggambar:
Mari kita analisis angka ini:
Catatan: Aturan HB memastikan bahwa hasil operasi sebelumnya terlihat oleh operasi berikutnya.
Oleh karena itu, pada applet di atas, Thread B sepenuhnya menyadari modifikasi NUM oleh Thread A - bahkan jika NUM tidak dimodifikasi dengan volatile.
Dengan cara ini, kami menggunakan prinsip HB untuk mewujudkan operasi sinkron dari suatu variabel, yaitu, dalam lingkungan multi-threaded, kami memastikan keamanan modifikasi bersamaan dari variabel bersama. Dan tidak ada java primitif untuk variabel ini: volatil dan disinkronkan dan CAS (dengan asumsi itu diperhitungkan).
Ini mungkin tampak tidak aman (sebenarnya aman) dan mungkin tidak mudah dimengerti. Karena semua ini diimplementasikan oleh protokol cache dan penghalang memori di HB yang mendasarinya.
Aturan lain untuk mencapai sinkronisasi
Implementasi Menggunakan Aturan Pengakhiran Thread:
statis int a = 1; public static void main (string [] args) {thread tb = new thread (() -> {a = 2;}); Thread ta = thread baru (() -> {coba {tb.join ();} catch (InterruptedException e) {// no} system.out.println (a);}); ta.start (); tb.start (); } Gunakan aturan mulai utas untuk diimplementasikan:
statis int a = 1; public static void main (string [] args) {thread tb = thread baru (() -> {System.out.println (a);}); Thread ta = thread baru (() -> {tb.start (); a = 2;}); ta.start (); }Kedua operasi ini juga dapat memastikan visibilitas variabel a.
Ini benar -benar menumbangkan konsep sebelumnya. Dalam konsep sebelumnya, jika suatu variabel tidak dimodifikasi oleh volatile atau final, maka bacaan dan penulisannya di bawah multi -threading jelas tidak aman - karena akan ada cache, yang mengakibatkan pembacaan yang bukan yang terbaru.
Namun, dengan menggunakan HB, kita dapat mencapainya.
Meringkaskan
Meskipun judul artikel ini adalah untuk mewujudkan operasi sinkron variabel bersama melalui terjadi sebelum, tujuan utamanya adalah untuk memahami terjadi lebih dalam. Memahami konsepnya sebelum sebenarnya adalah untuk memastikan ketertiban operasi sebelumnya untuk operasi berikutnya dan visibilitas operasi menghasilkan lingkungan multi-threaded.
Pada saat yang sama, dengan secara fleksibel menggunakan aturan transitif dan kemudian menggabungkan aturan, dua utas dapat disinkronkan - implementasi variabel bersama yang ditentukan tanpa menggunakan primitif juga dapat memastikan visibilitas. Meskipun ini tampaknya tidak mudah dibaca, ini juga merupakan upaya.
Doug Lea memberikan praktik di JUC tentang cara menggabungkan aturan untuk mencapai sinkronisasi.
Misalnya, sinkronisasi kelas dalam dari versi lama Futuretask (menghilang), memodifikasi variabel volatil melalui metode tryreleaseshared, dan tryacquireshared membaca variabel volatil, yang memanfaatkan aturan volatile;
Ini memanfaatkan aturan pesanan program dengan menetapkan variabel hasil yang tidak mudah menguap sebelum tryreleaseshared dan kemudian membaca variabel hasil setelah tryacquireshared.
Ini memastikan visibilitas variabel hasil. Mirip dengan contoh pertama kami: Menggunakan aturan pesanan program dan aturan yang mudah menguap untuk mencapai visibilitas variabel normal.
Doug Lea sendiri mengatakan bahwa teknologi "penggunaan bantuan" ini sangat rentan terhadap kesalahan dan harus digunakan dengan hati -hati. Tetapi dalam beberapa kasus, "leverage" semacam ini sangat masuk akal.
Faktanya, BlockingQueue juga "digunakan" terjadi sebelum. Ingat aturan buka kunci? Saat membuka kunci terjadi, elemen internal harus terlihat.
Ada operasi lain di perpustakaan kelas yang juga "menggunakan" prinsip yang terjadi sebelum: wadah bersamaan, Countdownlatch, Semaphore, Masa Depan, Pelaksana, Cyclicbarrier, Penukar, dll.
Singkatnya, singkatnya:
Prinsip yang terjadi sebelum adalah inti dari JMM. Hanya ketika prinsip HB dipenuhi dapat memesan dan visibilitas dipastikan, jika tidak kompiler akan memesan ulang kode. HB bahkan mendefinisikan aturan untuk kunci dan volatile.
Dengan kombinasi aturan HB yang tepat, penggunaan variabel bersama biasa yang benar dapat dicapai.
Oke, di atas adalah seluruh konten artikel ini. Saya berharap konten artikel ini memiliki nilai referensi tertentu untuk studi atau pekerjaan semua orang. Jika Anda memiliki pertanyaan, Anda dapat meninggalkan pesan untuk berkomunikasi. Terima kasih atas dukungan Anda ke wulin.com.