Konsep bebas-kunci telah disebutkan dalam pengenalan [Java 1 1]. Karena ada sejumlah besar aplikasi bebas kunci dalam kode sumber JDK, bebas kunci dikenalkan di sini.
1 Penjelasan terperinci tentang prinsip kelas tanpa kunci
1.1 Cas
Proses algoritma CAS adalah sebagai berikut: Ini berisi 3 parameter CAS (V, E, N). V mewakili variabel yang akan diperbarui, E mewakili nilai yang diharapkan, dan N mewakili nilai baru. Hanya jika v
Ketika nilai sama dengan nilai E, nilai V akan diatur ke N. Jika nilai V berbeda dari nilai E, itu berarti bahwa utas lain telah melakukan pembaruan, dan utas saat ini tidak melakukan apa pun. Akhirnya, CAS mengembalikan nilai sebenarnya dari operasi V. CAS saat ini dilakukan dengan sikap optimis, dan selalu percaya bahwa itu dapat berhasil menyelesaikan operasi. Ketika beberapa utas mengoperasikan variabel menggunakan CAS secara bersamaan, hanya satu yang akan menang dan memperbarui dengan sukses, dan sisanya akan gagal. Utas yang gagal tidak akan ditangguhkan, hanya diberitahu bahwa kegagalan diizinkan, dan diizinkan untuk mencoba lagi, dan tentu saja utas yang gagal juga akan memungkinkan operasi untuk ditinggalkan. Berdasarkan prinsip ini, CAS
Operasi terkunci segera, dan utas lain juga dapat mendeteksi gangguan ke utas saat ini dan menanganinya dengan tepat.
Kami akan menemukan bahwa ada terlalu banyak langkah di CAS. Mungkinkah setelah menilai bahwa V dan E adalah sama, ketika kami akan menetapkan nilai, kami mengganti utas dan mengubah nilainya. Apa yang menyebabkan ketidakkonsistenan data?
Faktanya, kekhawatiran ini berlebihan. Seluruh proses operasi CAS adalah operasi atom, yang diselesaikan dengan instruksi CPU.
1.2 Instruksi CPU
Instruksi CPU CAS adalah CMMPXCHG
Kode instruksi adalah sebagai berikut:
/ * Accumulator = al, AX, atau EAX, tergantung pada apakah perbandingan byte, Word, atau Doubleword sedang dilakukan */ if (accumulator == tujuan) {zf = 1; Tujuan = sumber; } else {zf = 0; akumulator = tujuan; } Jika nilai target sama dengan nilai dalam register, bendera lompatan diatur dan data asli diatur ke target. Jika Anda tidak menunggu, Anda tidak akan mengatur bendera lompat.
Java menyediakan banyak kelas bebas kunci, jadi mari kita perkenalkan kelas bebas kunci di bawah ini.
2 tidak berguna
Kita sudah tahu bahwa bebas kunci jauh lebih efisien daripada memblokir. Mari kita lihat bagaimana Java mengimplementasikan kelas-kelas bebas kunci ini.
2.1. Atomicinteger
Atomicinteger, seperti integer, keduanya mewarisi kelas angka
AtomicInteger kelas publik memperluas bilangan mengimplementasikan java.io.serializable
Ada banyak operasi CAS di Atomicinteger, yang khas adalah:
Publik Final Boolean CompareEndset (int hare, int update) {
return unsafe.comppareandswapint (ini, valueOffset, harapkan, perbarui);
}
Di sini kami akan menjelaskan metode yang tidak aman. Ini berarti bahwa jika nilai variabel yang offset pada kelas ini adalah ValueOffset sama dengan nilai yang diharapkan, maka atur nilai variabel ini untuk diperbarui.
Faktanya, variabel dengan nilai offset adalah nilai
static {try {valueOffset = uncafe.objectFieldOffset (atomicinteger.class.getDeclaredField ("value")); } catch (Exception ex) {throw new error (ex); }}Kami telah mengatakan sebelumnya bahwa CAS mungkin gagal, tetapi biaya kegagalan sangat kecil, sehingga implementasi umum berada dalam lingkaran tak terbatas sampai berhasil.
Public final int getAndIncrement () {for (;;) {int current = get (); int next = arus + 1; if (compareEndset (saat ini, selanjutnya)) Mengembalikan arus; }}2.2 Tidak aman
Dari nama kelas, kita dapat melihat bahwa operasi yang tidak aman adalah operasi yang tidak aman, seperti:
Tetapkan nilai sesuai dengan offset (saya telah melihat fungsi ini di AtomicInteger yang baru saja diperkenalkan)
park () (hentikan utas ini, akan disebutkan di blog mendatang)
Operasi CAS yang mendasari API non-publik dapat sangat berbeda dalam versi JDK yang berbeda.
2.3. Referensi Atomik
Atomicinteger telah disebutkan sebelumnya, dan tentu saja Atomicboolean, Atomiclong, dll. Semuanya serupa.
Yang ingin kami perkenalkan di sini adalah atomikreferensi.
Atomicreference adalah kelas templat
Atomicreference kelas publik <v> mengimplementasikan java.io.serializable
Ini dapat digunakan untuk merangkum semua jenis data.
Misalnya, string
tes paket; import java.util.concurrent.atomic.atomicreference; tes kelas publik {public final static atomicreference <string> atomicstring = atomicreference baru <string> ("hosee"); public static void main (string [] args) {for (int i = 0; i <10; i ++) {final int num = i; utas baru () {public void run () {coba {thread.sleep (math.abs ((int) math.random ()*100)); } catch (Exception e) {E.PrintStackTrace (); } if (atomicString.ComeeAndset ("hosee", "ztk")) {System.out.println (thread.currentThread (). getId () + "ubah nilai"); } else {System.out.println (thread.currentThread (). getId () + "gagal"); }}; }.awal(); }}}hasil:
10 -gagal
13 -Dikenakan
Nilai 9change
11 -gagal
12 -gagal
15 gagal
17 -retak
14 -gagal
16 -retak
18 -retak
Anda dapat melihat bahwa hanya satu utas yang dapat memodifikasi nilainya, dan utas selanjutnya tidak dapat memodifikasinya lagi.
2.4.atomicstampedreference
Kami akan menemukan bahwa masih ada masalah dengan operasi CAS.
Misalnya, metode Incrementandget AtomicInteger sebelumnya
public final int incrementandget () {for (;;) {int current = get (); int next = arus + 1; if (compareEndset (saat ini, selanjutnya)) kembali selanjutnya; }} Misalkan nilai saat ini = 1 Saat utas int arus = get () dijalankan, beralih ke utas lain, utas ini mengubah 1 menjadi 2, dan kemudian utas lain mengubah 2 menjadi 1 lagi. Saat ini, beralih ke utas awal. Karena nilainya masih sama dengan 1, operasi CAS masih dapat dilakukan. Tentu saja, tidak ada masalah dengan penambahan. Jika ada beberapa kasus, proses seperti itu tidak akan diizinkan ketika sensitif terhadap keadaan data.
Pada saat ini, kelas AtomicstampedReference diperlukan.
Ini mengimplementasikan kelas pasangan secara internal untuk merangkum nilai dan cap waktu.
pasangan kelas statis privat <T> {referensi T akhir; stempel int terakhir; private pair (T referensi, int stempel) {this.reference = referensi; this.stamp = cap; } static <T> Pair <T> dari (T referensi, int cap) {return pair baru <T> (referensi, stempel); }}Gagasan utama kelas ini adalah menambahkan cap waktu untuk mengidentifikasi setiap perubahan.
// Bandingkan Parameter Pengaturan adalah: Nilai yang Diharapkan Menulis Nilai Baru Mengharapkan Waktu Waktu Baru
Public boolean compareANDSet (v diharapkan reference, v newreference, int happowedstamp, int newstamp) {pair <v> current = pair; return diharapkan reference == current.reference && diharapkan -amp == current.stamp && ((newReference == current.reference && newStamp == current.stamp) || caspair (saat ini, pair.of (newReference, newStamp))); } Ketika nilai yang diharapkan sama dengan nilai saat ini dan cap waktu yang diharapkan sama dengan cap waktu saat ini, nilai baru ditulis dan cap waktu baru diperbarui.
Berikut adalah skenario yang menggunakan atomicstampedreference. Ini mungkin tidak cocok, tetapi saya tidak bisa membayangkan skenario yang bagus.
Latar belakang adegan adalah bahwa perusahaan mengisi ulang pengguna dengan saldo rendah secara gratis, tetapi setiap pengguna hanya dapat mengisi ulang sekali.
tes paket; impor java.util.concurrent.atomic.atomicstampedreference; tes kelas publik {static atomicstampedreference <Integer> uang = atomicstampedreference baru <Integer> (19, 0); public static void main (string [] args) {for (int i = 0; i <3; i ++) {final int timeStamp = money.getStamp (); utas baru () {public void run () {while (true) {while (true) {integer m = money.getReference (); if (m <20) {if (money.compareandset (m, m + 20, timestamp, timestamp + 1)) {System.out.println ("Mengisi ulang dengan sukses, menyeimbangkan:" + money.getReference ()); merusak; }} else {break; }}}}}}; }.awal(); } utas baru () {public void run () {for (int i = 0; i <100; i ++) {while (true) {int timestamp = money.getStamp (); Integer m = money.getReference (); if (m> 10) {if (money.compareandset (m, m - 10, timestamp, timestamp + 1)) {System.out.println ("dikonsumsi 10 yuan, saldo:" + money.getReference ()); merusak; }} else {break; }} coba {thread.sleep (100); } catch (Exception e) {// todo: menangani pengecualian}}}}; }.awal(); }}Jelaskan kodenya, ada 3 utas mengisi ulang pengguna. Ketika keseimbangan pengguna kurang dari 20, isi ulang pengguna 20 yuan. Ada 100 utas yang dikonsumsi, masing -masing menghabiskan 10 yuan. Pengguna awalnya memiliki 9 yuan. Saat menggunakan atomicstampedreference untuk mengimplementasikannya, pengguna hanya akan diisi ulang sekali, karena setiap operasi menyebabkan cap waktu +1. Hasil Menjalankan:
Recharge berhasil, keseimbangan: 39
Konsumsi 10 yuan, keseimbangan: 29
Konsumsi 10 yuan, keseimbangan: 19
Konsumsi 10 yuan, saldo: 9
Jika Anda menggunakan atomicreference <Integer> atau integer atom untuk mengimplementasikannya, itu akan menyebabkan beberapa pengisian ulang.
Recharge berhasil, keseimbangan: 39
Konsumsi 10 yuan, keseimbangan: 29
Konsumsi 10 yuan, keseimbangan: 19
Recharge berhasil, keseimbangan: 39
Konsumsi 10 yuan, keseimbangan: 29
Konsumsi 10 yuan, keseimbangan: 19
Recharge berhasil, keseimbangan: 39
Konsumsi 10 yuan, keseimbangan: 29
2.5. AtomicIntegerarray
Dibandingkan dengan AtomicInteger, implementasi array hanyalah sebuah subskrip tambahan.
Publik Final Boolean CompareEndset (int i, int hare, int update) {
return compareandsetraw (checkedbyteoffset (i), harapkan, perbarui);
}
Interiornya hanya merangkum array normal
array int [] final pribadi;
Yang menarik di sini adalah bahwa nol -nol utama dari angka biner digunakan untuk menghitung offset dalam array.
shift = 31 - integer.numberofleadingzeros (skala);
Nol terkemuka berarti bahwa misalnya, 8 bit mewakili 12.00001100, maka nol utama adalah jumlah 0 di depan 1, yaitu 4.
Cara menghitung offset tidak diperkenalkan di sini.
2.6. Atomicintegerfieldupdater
Fungsi utama kelas AtomicIntegerFieldUpdater adalah untuk memungkinkan variabel biasa juga menikmati operasi atom.
Misalnya, awalnya ada variabel yang merupakan tipe int, dan variabel ini diterapkan di banyak tempat. Namun, dalam skenario tertentu, jika Anda ingin mengubah jenis int menjadi AtomicInteger, jika Anda mengubah jenis secara langsung, Anda harus mengubah aplikasi di tempat lain. Atomicintegerfieldupdater dirancang untuk menyelesaikan masalah seperti itu.
Tes Paket; Impor java.util.concurrent.atomic.atomicinteger; import java.util.concurrent.atomic.atomicintegerfieldupdater; tes kelas publik {public static class v {int id; skor int yang mudah menguap; int int getscore () {skor pengembalian; } public void setscore (skor int) {this.score = skor; }} public final static atomicintegerfieldupdater <v> vv = atomicintegerfieldupdater.newupdater (v.class, "skor"); atomicinteger statis publik allscore = atomicinteger baru (0); public static void main (string [] args) melempar interruptedException {final v stu = new v (); Thread [] t = utas baru [10000]; untuk (int i = 0; i <10000; i ++) {t [i] = utas baru () {@Override public void run () {if (math.random ()> 0.4) {vv.incrementandget (Stu); allscore.incrementandget (); }}}; t [i] .start (); } untuk (int i = 0; i <10000; i ++) {t [i] .join (); } System.out.println ("score ="+stu.getscore ()); System.out.println ("allscore ="+allscore); }} Kode di atas mengubah skor menggunakan AtomicIntegerfieldUpdater menjadi AtomicInteger. Pastikan keamanan utas.
Allscore digunakan di sini untuk memverifikasi. Jika skor dan nilai allscore sama, itu berarti itu aman.
Catatan: