1. Pengenalan
Ringkasnya, keadaan yang dapat diubah bersama sangat mudah menimbulkan masalah apabila berlaku persetujuan bersama. Sekiranya akses ke objek yang dapat diubah bersama tidak dikendalikan dengan baik, aplikasi dapat dengan cepat menjadi rentan terhadap beberapa kesalahan serentak yang sukar dikesan.
Dalam artikel ini, kami akan mengkaji semula penggunaan kunci untuk menangani akses serentak, meneroka beberapa kelemahan yang berkaitan dengan kunci, dan akhirnya, memperkenalkan pemboleh ubah atom sebagai alternatif.
2. Kunci
Mari lihat kelas:
public class Counter { int counter; public void increment() { counter++; } }
Sekiranya persekitaran berulir tunggal, ini berfungsi dengan sempurna; namun, sebaik sahaja kita membiarkan lebih daripada satu utas menulis, kita mula mendapat hasil yang tidak konsisten.
Ini kerana operasi kenaikan sederhana ( kaunter ++ ), yang mungkin kelihatan seperti operasi atom, tetapi sebenarnya adalah gabungan dari tiga operasi: memperoleh nilai, kenaikan, dan menulis kembali nilai yang dikemas kini.
Sekiranya dua utas cuba mendapatkan dan mengemas kini nilainya pada masa yang sama, ia akan mengakibatkan kehilangan kemas kini.
Salah satu cara untuk menguruskan akses ke objek adalah dengan menggunakan kunci. Ini dapat dicapai dengan menggunakan kata kunci yang diselaraskan dalam tandatangan kaedah kenaikan . Kata kunci yang diselaraskan memastikan bahawa hanya satu utas yang dapat memasukkan kaedah pada satu masa (untuk mengetahui lebih lanjut mengenai Penguncian dan Penyegerakan rujuk - Panduan untuk Kata Kunci Diselaraskan di Java):
public class SafeCounterWithLock { private volatile int counter; public synchronized void increment() { counter++; } }
Selain itu, kita perlu menambahkan kata kunci yang tidak stabil untuk memastikan keterlihatan rujukan yang betul di antara utas.
Menggunakan kunci menyelesaikan masalah. Walau bagaimanapun, persembahannya mendapat kejayaan.
Apabila banyak utas berusaha mendapatkan kunci, salah satu daripadanya akan menang, sementara benang yang lain disekat atau digantung.
Proses menangguhkan dan kemudian menyambung semula benang sangat mahal dan mempengaruhi keseluruhan kecekapan sistem.
Dalam program kecil, seperti penghitung , waktu yang dihabiskan dalam pertukaran konteks mungkin menjadi lebih banyak daripada pelaksanaan kod yang sebenarnya, sehingga sangat mengurangi efisiensi keseluruhan.
3. Operasi Atom
Terdapat cabang penyelidikan yang difokuskan untuk membuat algoritma tanpa penyekat untuk persekitaran serentak. Algoritma ini mengeksploitasi arahan mesin atom tahap rendah seperti membandingkan-dan-pertukaran (CAS), untuk memastikan integriti data.
Operasi CAS biasa berfungsi pada tiga operasi:
- Lokasi memori di mana hendak beroperasi (M)
- Nilai jangkaan sedia ada (A) pemboleh ubah
- Nilai baru (B) yang perlu ditetapkan
Operasi CAS secara automatik mengemas kini nilai dalam M hingga B, tetapi hanya jika nilai yang ada di M sepadan dengan A, jika tidak, tidak ada tindakan yang diambil.
Dalam kedua kes tersebut, nilai yang ada dalam M dikembalikan. Ini menggabungkan tiga langkah - mendapatkan nilai, membandingkan nilai, dan mengemas kini nilainya - ke dalam satu operasi tahap mesin.
Apabila berbilang utas cuba mengemas kini nilai yang sama melalui CAS, salah satu daripadanya menang dan mengemas kini nilainya. Walau bagaimanapun, tidak seperti kes kunci, tidak ada benang lain yang digantung ; sebaliknya, mereka dimaklumkan bahawa mereka tidak berjaya mengemas kini nilainya. Benang kemudian boleh terus melakukan kerja lebih jauh dan pertukaran konteks dihindari sepenuhnya.
Akibat lain ialah logik program teras menjadi lebih kompleks. Ini kerana kita harus menangani senario ketika operasi CAS tidak berjaya. Kita boleh mencubanya berulang kali sehingga berjaya, atau kita tidak dapat berbuat apa-apa dan terus bergantung pada kes penggunaan.
4. Pemboleh ubah Atom di Jawa
Kelas pemboleh ubah atom yang paling biasa digunakan di Java ialah AtomicInteger, AtomicLong, AtomicBoolean, dan AtomicReference. Kelas-kelas ini mewakili rujukan int , panjang , boolean, dan objek yang masing-masing dapat dikemas kini secara atom. Kaedah utama yang dinyatakan oleh kelas ini adalah:
- get () - mendapat nilai dari memori, supaya perubahan yang dibuat oleh utas lain dapat dilihat; bersamaan dengan membaca pemboleh ubah tidak stabil
- set () - menuliskan nilai ke memori, supaya perubahan dapat dilihat pada utas lain; setara dengan menulis pemboleh ubah tidak stabil
- lazySet () - akhirnya menuliskan nilai ke memori, mungkin disusun semula dengan operasi memori yang berkaitan seterusnya. Satu kes penggunaan adalah membatalkan rujukan, demi pengumpulan sampah, yang tidak akan dapat diakses lagi. Dalam kes ini, prestasi yang lebih baik dicapai dengan melambatkan penulisan tidak menentu
- membandingkanAndSet () - sama seperti yang dijelaskan dalam bahagian 3, mengembalikan benar apabila berjaya, yang lain palsu
- lemahCompareAndSet () - sama seperti yang dijelaskan dalam bahagian 3, tetapi lebih lemah dalam pengertian, bahawa ia tidak membuat berlaku sebelum pesanan. Ini bermaksud bahawa ia tidak semestinya dapat melihat kemas kini yang dibuat pada pemboleh ubah lain. Sehingga Java 9, kaedah ini tidak digunakan lagi dalam semua implementasi atom yang memihak kepada lemahCompareAndSetPlain () . Kesan ingatan lemahCompareAndSet () adalah jelas tetapi namanya menyiratkan kesan memori yang tidak menentu. Untuk mengelakkan kekeliruan ini, mereka menggunakan kaedah ini dan menambahkan empat kaedah dengan kesan memori yang berbeza seperti lemahCompareAndSetPlain () atau lemahCompareAndSetVolatile ()
Kaunter benang selamat yang dilaksanakan dengan AtomicInteger ditunjukkan dalam contoh di bawah:
public class SafeCounterWithoutLock { private final AtomicInteger counter = new AtomicInteger(0); public int getValue() { return counter.get(); } public void increment() { while(true) { int existingValue = getValue(); int newValue = existingValue + 1; if(counter.compareAndSet(existingValue, newValue)) { return; } } } }
Seperti yang anda lihat, kami mencuba semula operasi membandingkanAndSet dan sekali lagi gagal, kerana kami ingin menjamin bahawa kaedah panggilan ke kenaikan selalu meningkatkan nilai sebanyak 1.
5. Kesimpulan
Dalam tutorial ringkas ini, kami menerangkan cara alternatif untuk menangani persamaan di mana kelemahan yang berkaitan dengan penguncian dapat dielakkan. Kami juga melihat kaedah utama yang didedahkan oleh kelas pemboleh ubah atom di Jawa.
Seperti biasa, contohnya terdapat di GitHub.
Untuk meneroka lebih banyak kelas yang menggunakan algoritma tanpa penyekat secara dalaman, rujuk panduan untuk ConcurrentMap.