Pertama, mari kita perkenalkan beberapa kunci optimis dan kunci pesimistis:
Kunci pesimistis: Selalu anggap kasus terburuk. Setiap kali saya pergi untuk mendapatkan data, saya pikir orang lain akan memodifikasinya, jadi saya akan menguncinya setiap kali saya mendapatkan data, sehingga orang lain akan memblokirnya sampai mendapatkan kunci. Basis data relasional tradisional menggunakan banyak mekanisme penguncian seperti itu, seperti kunci baris, kunci tabel, dll., Baca kunci, tulis kunci, dll., Yang terkunci sebelum melakukan operasi. Misalnya, implementasi kata kunci kata kunci yang disinkronkan kata kunci yang disinkronkan di java juga merupakan kunci pesimistis.
Kunci Optimis: Seperti namanya, itu berarti sangat optimis. Setiap kali saya pergi untuk mendapatkan data, saya pikir orang lain tidak akan memodifikasinya, jadi saya tidak akan menguncinya. Namun, ketika memperbarui, saya akan menilai apakah orang lain telah memperbarui data selama periode ini, dan dapat menggunakan nomor versi dan mekanisme lainnya. Kunci optimis cocok untuk jenis aplikasi multi-membaca, yang dapat meningkatkan throughput. Misalnya, database menyediakan kunci optimis yang mirip dengan mekanisme Write_Condition, tetapi semuanya sebenarnya disediakan oleh kunci optimis. Di Java, kelas variabel atom di bawah java.util.concurrent.atomic Paket diimplementasikan oleh CAS menggunakan kunci optimis.
Implementasi lock-cas optimis (bandingkan dan swap):
Masalah penguncian:
Sebelum JDK1.5, Java mengandalkan kata kunci yang disinkronkan untuk memastikan sinkronisasi. Dengan cara ini, dengan menggunakan protokol kunci yang konsisten untuk mengoordinasikan akses ke keadaan bersama, dapat memastikan bahwa tidak peduli utas mana yang memegang kunci variabel bersama, ia menggunakan metode eksklusif untuk mengakses variabel -variabel ini. Ini adalah semacam kunci eksklusif. Kunci eksklusif sebenarnya adalah semacam kunci pesimistis, sehingga dapat dikatakan bahwa disinkronkan adalah kunci pesimistis.
Mekanisme kunci pesimistis memiliki masalah berikut:
1. Di bawah kompetisi multi-utara, menambah dan melepaskan kunci akan menyebabkan lebih banyak konteks switching dan penjadwalan penundaan, menyebabkan masalah kinerja.
2. Benang yang menahan kunci akan menyebabkan semua utas lain yang mengharuskan kunci ini menggantung.
3. Jika utas dengan prioritas tinggi menunggu utas dengan prioritas rendah untuk melepaskan kunci, itu akan menyebabkan inversi prioritas, menyebabkan risiko kinerja.
Dibandingkan dengan masalah kunci pesimistis ini, kunci lain yang lebih efektif adalah kunci yang optimis. Faktanya, penguncian yang optimis adalah: Setiap kali Anda tidak menambahkan kunci, tetapi Anda menyelesaikan operasi dengan asumsi tidak ada konflik bersamaan. Jika konflik bersamaan gagal, coba lagi sampai berhasil.
Kunci Optimis:
Penguncian optimis telah disebutkan di atas, tetapi sebenarnya semacam pemikiran. Dibandingkan dengan kunci pesimistis, kunci optimis mengasumsikan bahwa data umumnya tidak akan menyebabkan konflik bersamaan, jadi ketika data diajukan dan diperbarui, itu akan secara resmi mendeteksi apakah data memiliki konflik bersamaan. Jika konflik bersamaan ditemukan, informasi yang salah pengguna akan dikembalikan dan pengguna memutuskan bagaimana melakukannya.
Konsep kunci optimis yang disebutkan di atas sebenarnya telah menjelaskan detail implementasinya yang spesifik: ini terutama mencakup dua langkah: deteksi konflik dan pembaruan data. Salah satu metode implementasi yang khas adalah Bandingkan dan SWAP (CAS).
CAS:
CAS adalah teknologi penguncian yang optimis. Ketika banyak utas mencoba menggunakan CAS untuk memperbarui variabel yang sama secara bersamaan, hanya satu dari utas yang dapat memperbarui nilai variabel, sementara utas lainnya gagal. Utas yang gagal tidak akan ditangguhkan, tetapi akan diberitahu bahwa kompetisi ini telah gagal dan dapat mencoba lagi.
Operasi CAS berisi tiga operan - lokasi memori (v) yang perlu dibaca dan ditulis, nilai asli yang diharapkan (a) untuk perbandingan, dan nilai baru (b) yang akan ditulis. Jika nilai posisi memori V cocok dengan nilai asli yang diharapkan, prosesor akan secara otomatis memperbarui nilai posisi ke nilai baru B. Jika tidak, prosesor tidak akan melakukan apa pun. Dalam kedua kasus tersebut, ia mengembalikan nilai lokasi itu sebelum Petunjuk CAS. (Dalam beberapa kasus khusus CAS, hanya apakah CAS berhasil atau tidak, tanpa mengekstraksi nilai saat ini.) CAS secara efektif menyatakan bahwa "Saya pikir posisi V harus berisi nilai A; jika mengandung, letakkan B di posisi ini; jika tidak, jangan mengubah posisi, cukup beri tahu saya nilai saat ini dari posisi ini." Ini sebenarnya sama dengan prinsip pemeriksaan konflik + pembaruan data dari kunci optimis.
Izinkan saya menekankan di sini bahwa penguncian optimis adalah semacam pemikiran. CAS adalah cara untuk mewujudkan ide ini.
Dukungan Java untuk CAS:
Java.util.concurrent (JUC) baru di JDK1.5 dibangun di atas CAS. Dibandingkan dengan algoritma pemblokiran seperti disinkronkan, CAS adalah implementasi umum dari algoritma non-blocking. Karena itu, JUC telah sangat meningkatkan kinerjanya.
Ambil AtomicInteger di java.util.concurrent sebagai contoh untuk melihat bagaimana memastikan keamanan utas tanpa menggunakan kunci. Kami terutama memahami metode getAndincrement, yang setara dengan operasi ++ I.
AtomicInteger kelas publik memperluas bilangan mengimplementasikan java.io.serializable {private volatile int value; Public final int get () {nilai pengembalian; } public final int getAndIncrement () {for (;;) {int current = get (); int next = arus + 1; if (compareEndset (saat ini, selanjutnya)) Mengembalikan arus; }} public final boolean compareEndset (int happeC, int update) {return unsafe.comppareandswapint (this, valueOffset, harapkan, perbarui); }}Dalam mekanisme tanpa kunci, nilai bidang harus digunakan untuk memastikan bahwa data antara utas adalah visibilitas. Dengan cara ini, Anda dapat membaca secara langsung ketika Anda mendapatkan nilai variabel. Lalu mari kita lihat bagaimana ++ saya selesai.
GetAndIncrement menggunakan operasi CAS, dan setiap kali Anda membaca data dari memori, kemudian melakukan operasi CAS pada data ini dan hasilnya setelah +1. Jika berhasil, hasilnya akan dikembalikan, jika tidak coba lagi sampai berhasil.
Compareandset menggunakan JNI (Java Native Interface) untuk menyelesaikan pengoperasian instruksi CPU:
Publik Final Boolean CompareEndset (int hare, int update) {return unsafe.ComeeAndsWapint (ini, valueOffset, harapkan, perbarui); }di mana tidak aman. mirip dengan logika berikut:
if (this == harapkan) {this = update return true; } else {return false; }Jadi bagaimana cara membandingkan ini == Harapkan, ganti ini = perbarui, compareandswapint untuk mencapai atomisitas dari dua langkah ini? Merujuk pada prinsip -prinsip CAS
Prinsip CAS:
CAS diimplementasikan dengan memanggil kode JNI. CompareandsWapint diimplementasikan dengan menggunakan C untuk memanggil instruksi CPU yang mendasarinya.
Berikut ini menjelaskan prinsip implementasi CAS dari analisis CPU yang lebih umum digunakan (Intel x86).
Berikut adalah kode sumber dari metode compareanddswapint () dari Sun.misc.unsafe Class:
Public Final Boolean CompareeAndsWapint (Object O, Long Offset, Int diharapkan, int x);
Anda dapat melihat bahwa ini adalah panggilan metode lokal. Kode C ++ yang disebut metode lokal ini di JDK adalah:
#define lock_if_mp (mp) __asm cmmp mp, 0 / __asm je l0 / __asm _emit 0xf0 / __asm l0: inline jint atomic :: cmpxchg (jint exchange_value, volatile jint* dest, jint comparpar_value) { / / alternatif forpar forpare forpare forpare, jint comparpare, jint comparpare, jint comparpare) { / / alternatif OS :: is_mp (); __asm {mov edx, dest mov ecx, exchange_value mov eax, compare_value lock_if_mp (mp) cmmpxchg dword ptr [edx], ecx}}Seperti yang ditunjukkan pada kode sumber di atas, program akan memutuskan apakah akan menambahkan awalan kunci ke instruksi CMMPXCHG berdasarkan jenis prosesor saat ini. Jika program berjalan pada multiprosesor, tambahkan awalan kunci ke instruksi CMMPXCHG. Sebaliknya, jika program berjalan pada prosesor tunggal, awalan kunci dihilangkan (prosesor tunggal itu sendiri mempertahankan konsistensi berurutan dalam prosesor tunggal dan tidak memerlukan efek penghalang memori yang disediakan oleh awalan kunci).
CAS Kekurangan:
1. Pertanyaan ABA:
Misalnya, jika satu utas mengambil dari posisi memori V, maka utas lain dua juga mengeluarkan A dari memori, dan dua melakukan beberapa operasi dan menjadi B, dan kemudian dua memutar data pada posisi V A. Pada saat ini, utas satu melakukan operasi CAS dan menemukan bahwa A masih dalam memori, dan kemudian satu beroperasi dengan sukses. Meskipun operasi CAS seseorang berhasil, mungkin ada masalah tersembunyi. Seperti yang ditunjukkan di bawah ini:
Ada tumpukan yang diimplementasikan dengan daftar tertaut satu arah, dengan bagian atas tumpukan menjadi A. Pada saat ini, Thread T1 sudah tahu bahwa A.Next adalah B, dan kemudian berharap untuk mengganti bagian atas tumpukan dengan B dengan CAS:
head.comppareandset (a, b);
Sebelum T1 menjalankan instruksi di atas, Thread T2 mengintervensi, menempatkan A dan B keluar dari tumpukan, dan kemudian pushd, c dan A. Pada saat ini, struktur tumpukan adalah sebagai berikut, dan objek B dalam keadaan bebas saat ini:
Pada saat ini, giliran Thread T1 untuk melakukan operasi CAS. Deteksi menemukan bahwa bagian atas tumpukan masih A, jadi CAS berhasil, dan bagian atas tumpukan menjadi B, tetapi pada kenyataannya B.Next adalah nol, jadi situasinya saat ini menjadi:
Hanya ada satu elemen B di tumpukan, dan daftar tertaut yang terdiri dari C dan D tidak ada lagi di tumpukan. C dan D dibuang tanpa alasan.
Mulai dari Java 1.5, paket atom JDK menyediakan class atomicstampedreference untuk menyelesaikan masalah ABA. Metode CompareANDSet dari kelas ini adalah untuk pertama -tama memeriksa apakah referensi saat ini sama dengan referensi yang diharapkan dan apakah bendera saat ini sama dengan bendera yang diharapkan. Jika semuanya sama, referensi dan nilai bendera diatur ke nilai yang diperbarui dengan cara atom.
Public Boolean CompareANDSet (v diharapkan Reference, // Referensi yang Diharapkan v Newreference, // Referensi yang Diperbarui Int diharapkan, // Bendera yang diharapkan int newstamp // bendera yang diperbarui)
Kode aplikasi aktual:
AtomicstampedReference statis pribadi <Integer> atomicstampedref = atomicstampedreference baru <Integer> (100, 0); ......... atomicstampedref.comppareandset (100, 101, stempel, stempel + 1);
2. Waktu siklus panjang dan overhead tinggi:
Spin Cas (jika gagal, itu akan dieksekusi bersepeda sampai berhasil) jika gagal untuk waktu yang lama, itu akan membawa overhead eksekusi yang bagus ke CPU. Jika JVM dapat mendukung instruksi jeda yang disediakan oleh prosesor, efisiensi akan ditingkatkan sampai batas tertentu. Instruksi jeda memiliki dua fungsi. Pertama, dapat menunda instruksi eksekusi pipa (de-pipeline) sehingga CPU tidak akan mengkonsumsi terlalu banyak sumber daya eksekusi. Waktu tunda tergantung pada versi implementasi spesifik. Pada beberapa prosesor, waktu tunda adalah nol. Kedua, dapat menghindari flush pipa CPU yang disebabkan oleh pelanggaran urutan memori saat keluar dari loop, sehingga meningkatkan efisiensi eksekusi CPU.
3. Hanya operasi atom dari variabel bersama yang dapat dijamin:
Saat melakukan operasi pada variabel bersama, kami dapat menggunakan metode CAS siklik untuk memastikan operasi atom. Namun, ketika mengoperasikan beberapa variabel bersama, CAS siklik tidak dapat menjamin atomisitas operasi. Pada saat ini, Anda dapat menggunakan kunci, atau ada trik, yaitu untuk menggabungkan beberapa variabel bersama menjadi variabel bersama untuk beroperasi. Misalnya, ada dua variabel bersama i = 2, j = a, gabungan ij = 2a, dan kemudian gunakan CAS untuk mengoperasikan IJ. Mulai dari Java 1.5, JDK menyediakan kelas atomikreferensi untuk memastikan atomisitas antara objek yang direferensikan. Anda dapat menempatkan beberapa variabel dalam satu objek untuk operasi CAS.
CAS dan skenario penggunaan yang disinkronkan:
1. Untuk situasi di mana ada lebih sedikit kompetisi sumber daya (konflik utas ringan), menggunakan kunci sinkronisasi yang disinkronkan untuk pemblokiran utas dan operasi switching dan switching antara status kernel negara-pengguna adalah pemborosan sumber daya CPU ekstra; Sementara CAS diimplementasikan berdasarkan perangkat keras, tidak perlu memasuki kernel, tidak perlu mengganti utas, dan kemungkinan operasi putaran lebih sedikit, sehingga kinerja yang lebih tinggi dapat diperoleh.
2. Untuk situasi di mana persaingan sumber daya serius (konflik utas yang parah), probabilitas putaran CAS relatif tinggi, yang membuang lebih banyak sumber daya CPU dan kurang efisien daripada disinkronkan.
Suplemen: Sinkronisasi telah ditingkatkan dan dioptimalkan setelah JDK1.6. Implementasi yang mendasari sinkronisasi terutama bergantung pada antrian bebas kunci. Ide dasarnya adalah untuk memblokir setelah putaran, terus bersaing untuk mengunci setelah pergantian kompetisi, sedikit mengorbankan keadilan, tetapi mendapatkan throughput tinggi. Ketika ada lebih sedikit konflik utas, kinerja serupa dapat diperoleh; Ketika ada konflik utas yang serius, kinerjanya jauh lebih tinggi daripada CAS.
Implementasi paket bersamaan:
Karena Java's CAS memiliki kedua semantik memori untuk membaca yang mudah menguap dan menulis volatile, sekarang ada empat cara untuk berkomunikasi antara utas Java:
1. Thread A menulis variabel volatil, dan kemudian Thread B membaca variabel volatil.
2. Thread A menulis variabel volatil, dan kemudian Thread B menggunakan CAS untuk memperbarui variabel volatil.
3. Thread A menggunakan CAS untuk memperbarui variabel volatil, dan kemudian Thread B menggunakan CAS untuk memperbarui variabel volatil ini.
4. Thread A menggunakan CAS untuk memperbarui variabel volatile, dan kemudian Thread B membaca variabel yang mudah menguap ini.
CAS Java menggunakan instruksi atom tingkat mesin yang efisien yang disediakan pada prosesor modern, yang melakukan operasi wrange-write pada memori secara atom, yang merupakan kunci untuk mencapai sinkronisasi dalam multiprosesor (pada dasarnya, mesin yang dapat mendukung setiap mesin yang dapat dikelupasikan secara berurutan adalah sebuah caning-wrange-t-wrange adalah sebuah mesin yang tidak bersinkonik yang tidak ada secara berurutan yang secara berurutan adalah sebuah calculate yang tidak ada secara berurutan yang dikalsulasikan secara berurutan. Operasi Read-Change-Write atom pada memori). Pada saat yang sama, baca/tulis dan CAS dari variabel volatil dapat mewujudkan komunikasi antar utas. Mengintegrasikan fitur -fitur ini bersama -sama membentuk landasan implementasi seluruh paket bersamaan. Jika kami dengan hati -hati menganalisis implementasi kode sumber dari paket bersamaan, kami akan menemukan pola implementasi umum:
1. Pertama, nyatakan variabel bersama menjadi tidak stabil;
2. Kemudian, gunakan pembaruan kondisi atom CAS untuk mencapai sinkronisasi antar utas;
3. Pada saat yang sama, komunikasi antara utas dicapai dengan menggunakan baca/tulis volatile dan semantik memori dari baca dan tulis yang mudah menguap di CAS.
AQS, struktur data non-blocking dan kelas variabel atom (kelas di java.util.concurrent.atomic Paket), kelas dasar dalam paket bersamaan ini diimplementasikan menggunakan pola ini, dan kelas tingkat tinggi dalam paket bersamaan dengan bergantung pada kelas dasar ini untuk diimplementasikan. Dari perspektif umum, diagram implementasi paket bersamaan adalah sebagai berikut:
CAS (penugasan objek di heap):
Java memanggil new object() untuk membuat objek, yang akan dialokasikan ke tumpukan JVM. Jadi bagaimana objek ini disimpan di tumpukan?
Pertama -tama, ketika new object() dieksekusi, berapa banyak ruang yang dibutuhkan objek ini sebenarnya ditentukan, karena berbagai tipe data di java dan berapa banyak ruang yang mereka ambil (jika Anda tidak jelas tentang prinsipnya, silakan google sendiri). Maka pekerjaan berikutnya adalah menemukan ruang di tumpukan untuk menyimpan objek ini.
Dalam kasus utas tunggal, umumnya ada dua strategi alokasi:
1. Pointer Collision: Ini umumnya berlaku untuk memori yang benar -benar teratur (apakah memori teratur tergantung pada strategi daur ulang memori). Tugas mengalokasikan ruang adalah hanya untuk memindahkan pointer seperti jarak ukuran objek di sisi memori bebas.
2. Daftar Gratis: Ini cocok untuk memori non-reguler. Dalam hal ini, JVM akan mempertahankan daftar memori untuk merekam area memori mana yang gratis dan berapa ukurannya. Saat mengalokasikan ruang ke objek, buka daftar gratis untuk meminta area yang sesuai dan kemudian mengalokasikannya.
Namun, tidak mungkin bagi JVM untuk berjalan dalam satu keadaan berulir sepanjang waktu, sehingga efisiensinya terlalu buruk. Karena ini bukan operasi atom ketika mengalokasikan memori ke objek lain, setidaknya langkah -langkah berikut diperlukan: menemukan daftar gratis, mengalokasikan memori, memodifikasi daftar gratis, dll., Yang tidak aman. Ada juga dua strategi untuk menyelesaikan masalah keamanan selama konkurensi:
1. CAS: Faktanya, mesin virtual menggunakan CAS untuk memastikan atomisitas operasi pembaruan dengan gagal mencoba lagi, dan prinsipnya sama seperti yang disebutkan di atas.
2. TLAB: Jika CAS digunakan, itu sebenarnya akan berdampak pada kinerja, jadi JVM telah mengusulkan strategi optimasi yang lebih maju: setiap utas pra-alokasi sepotong kecil memori dalam tumpukan Java, yang disebut buffer alokasi utas lokal (TLAB). Ketika utas perlu mengalokasikan memori di dalamnya, itu cukup untuk mengalokasikannya langsung pada TLAB, menghindari konflik utas. Hanya ketika memori buffer digunakan dan perlu merealokasi memori CAS akan dilakukan untuk mengalokasikan ruang memori yang lebih besar.
Apakah mesin virtual menggunakan TLAB dapat dikonfigurasi melalui parameter -XX:+/-UseTLAB (JDK5 dan versi yang lebih baru diaktifkan secara default).
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.