1. Usulkan masalah sinkronisasi
Misalkan kita menggunakan prosesor dual-core untuk menjalankan dua utas A dan B, Core 1 menjalankan utas A, dan Core 2 mengeksekusi utas B, kedua utas sekarang harus menambahkan 1 ke variabel anggota I dari objek bernama OBJ. Dengan asumsi bahwa nilai awal I adalah 0, secara teoritis, nilai saya harus menjadi 2 setelah dua utas berjalan, tetapi pada kenyataannya sangat mungkin bahwa hasilnya akan 1.
Mari kita analisis alasannya sekarang. Demi kesederhanaan analisis, kami tidak mempertimbangkan situasi cache. Faktanya, ada cache yang akan meningkatkan kemungkinan bahwa hasilnya adalah 1. Thread A membaca variabel I dalam memori ke dalam unit operasi aritmatika kernel 1, kemudian melakukan operasi penambahan, dan kemudian menulis hasil perhitungan kembali ke memori. Karena operasi di atas bukanlah operasi atom, selama Thread B membaca nilai i dalam memori sebelum utas A menulis nilai i dengan menambahkan 1 kembali ke memori (nilai i adalah 0 pada saat ini), maka hasil dari saya pasti akan 1. Karena nilai yang saya baca oleh Threads A dan B adalah 0, dan nilainya setelah menambah 1 hingga 1, dua.
Solusi yang paling umum adalah menggunakan kata kunci sinkronisasi untuk mengunci objek OBJ dengan kode yang menambahkan 1 ke kode I-terlihat dalam dua utas. Hari ini kami memperkenalkan solusi baru, yaitu menggunakan kelas terkait dalam paket atom untuk menyelesaikannya.
2. Dukungan Perangkat Keras ATomik
Dalam satu sistem prosesor (uniprocessor), operasi yang dapat diselesaikan dalam satu instruksi dapat dianggap "operasi atom" karena interupsi hanya dapat terjadi antara instruksi (karena penjadwalan utas perlu diselesaikan melalui interupsi). Ini juga merupakan alasan mengapa beberapa sistem instruksi CPU memperkenalkan test_and_set, test_and_clear dan instruksi lainnya untuk pengecualian sumber daya kritis. Ini berbeda dalam struktur multi-prosesor simetris, karena banyak prosesor berjalan secara mandiri dalam sistem, bahkan operasi yang dapat diselesaikan dalam satu instruksi dapat terganggu.
Pada platform X86, CPU menyediakan sarana untuk mengunci bus selama eksekusi instruksi. Ada lead #hlockpin pada chip CPU. Jika awalan "kunci" ditambahkan ke instruksi dalam program bahasa perakitan, kode mesin perakitan akan menyebabkan CPU menurunkan potensi #hlockpin saat menjalankan instruksi ini, dan melepaskannya sampai akhir instruksi ini, sehingga mengunci bus. Dengan cara ini, CPU lain di bus yang sama tidak dapat mengakses memori melalui bus untuk sementara waktu, memastikan atomisitas instruksi ini dalam lingkungan multiprosesor. Tentu saja, tidak semua instruksi dapat diawali dengan kunci. Hanya Tambah, ADC, dan, BTC, BTR, BTS, CMPXCHG, DEC, INC, NEG, NOT, OR, SBB, SUB, XOR, XADD, dan Instruksi XCHG dapat diawali dengan instruksi "LOCK" untuk mewujudkan operasi atom.
Operasi inti atom adalah CAS (CompareANDSet, diimplementasikan menggunakan instruksi CMPXCHG, yang merupakan instruksi atom). Instruksi ini memiliki tiga operan, nilai memori v dari variabel (singkatan nilai), nilai yang diharapkan saat ini dari variabel (singkatan pengecualian), nilai u dari variabel ingin memperbarui (singkatan dari pembaruan). Ketika nilai memori sama dengan nilai yang diharapkan saat ini, nilai yang diperbarui dari variabel ditimpa oleh variabel, dan kode pseudo dijalankan sebagai berikut.
if (v == e) {v = u return true} else {return false}Sekarang kami akan menggunakan operasi CAS untuk menyelesaikan masalah di atas. Thread B membaca variabel I dalam memori ke dalam variabel sementara (dengan asumsi bahwa nilai yang dibaca pada saat ini adalah 0), dan kemudian membaca nilai I ke unit operasi aritmatika Core1. Selanjutnya, tambahkan 1 untuk membandingkan apakah nilai dalam variabel sementara sama dengan nilai saat ini i. Jika nilai i dalam memori adalah sama dengan nilai hasil di unit operasi (mis. I+1) (perhatikan bahwa bagian ini adalah operasi CAS, itu adalah operasi atom, yang tidak dapat terganggu dan operasi CAS di utas lain tidak dapat dieksekusi pada saat yang sama), jika tidak, pelaksanaan instruksi gagal. Jika instruksi gagal, itu berarti bahwa utas A telah meningkatkan nilai I oleh 1. Dari ini kita dapat melihat bahwa jika nilai yang saya baca oleh kedua utas adalah 0 di awal, maka hanya satu operasi CAS utas yang dapat berhasil, karena operasi CAS tidak dapat dieksekusi secara bersamaan. Untuk utas yang gagal menjalankan operasi CAS, selama operasi CAS dieksekusi secara loopen, itu pasti akan berhasil. Anda dapat melihat bahwa tidak ada pemblokiran utas, yang pada dasarnya berbeda dari prinsip sinkronisasi.
3. Pengantar Paket Atom dan Analisis Kode Sumber
Fitur dasar dari kelas dalam paket atom adalah bahwa dalam lingkungan multi-threaded, ketika beberapa utas beroperasi pada variabel tunggal (termasuk tipe dasar dan jenis referensi) pada saat yang sama, itu eksklusif, yaitu, ketika banyak utas memperbarui nilai variabel pada saat yang sama, hanya satu utas yang dapat berhasil, dan utas yang tidak berhasil.
Metode inti dalam kelas Seri Atom akan memanggil beberapa metode lokal di kelas yang tidak aman. Kita perlu tahu terlebih dahulu bahwa satu hal adalah kelas yang tidak aman, dengan nama lengkapnya: sun.misc.unsafe. Kelas ini berisi sejumlah besar operasi pada kode C, termasuk banyak alokasi memori langsung dan panggilan operasi atom. Alasan mengapa itu ditandai sebagai non-aman adalah untuk memberi tahu Anda bahwa sejumlah besar panggilan metode di bidang ini akan memiliki risiko keamanan dan perlu digunakan dengan hati-hati, jika tidak itu akan menyebabkan konsekuensi serius. Misalnya, ketika mengalokasikan memori melalui tidak aman, jika Anda menentukan area tertentu sendiri, itu dapat menyebabkan beberapa petunjuk seperti C ++ untuk melewati batas ke proses lain.
Kelas dalam paket atom dapat dibagi menjadi 4 kelompok sesuai dengan tipe data operasional.
AtomicBoolean,AtomicInteger,AtomicLong
Jenis Dasar Operasi Atom untuk Safe Thread
AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
Pengoperasian atom yang aman dari jenis array, yang beroperasi tidak pada seluruh array, tetapi pada satu elemen dalam array
AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
Operasi yang aman-utas berdasarkan tipe dasar (bilangan bulat panjang, integer dan jenis referensi) dalam objek prinsip refleksi
AtomicReference,AtomicMarkableReference,AtomicStampedReference
Jenis referensi yang aman dan aman dan operasi atom dari jenis referensi yang mencegah masalah ABA
Kami umumnya menggunakan atomicinteger, atomicreference dan atomicstampedreference. Sekarang mari kita analisis kode sumber integer atom dalam paket atom. Kode sumber kelas lain pada prinsipnya serupa.
1. Konstruktor parameter
atomicinteger publik (int initialValue) {value = initialValue;}Seperti dapat dilihat dari fungsi konstruktor, nilainya disimpan dalam nilai variabel anggota
nilai int volatile pribadi;
Nilai variabel anggota dinyatakan sebagai tipe volatil, yang menunjukkan visibilitas di bawah beberapa utas, yaitu, modifikasi utas apa pun akan segera terlihat di utas lain.
2. Metode Compareandset (Nilai nilai dilewatkan melalui internal ini dan ValueOffset)
Publik Final Boolean CompareANDSET (int hare, int update) {return unsafe.ComeeAndsWapint (this, valueOffset, harapkan, update);}Metode ini adalah operasi CAS paling inti
3.GetAndset Metode, di mana metode CompareEndset disebut
publik final int getAndset (int newValue) {for (;;) {int current = get (); if (compareEndset (saat ini, newValue)) mengembalikan arus; }}Jika utas lain mengubah nilai nilai sebelum dieksekusi IF (CompareANDSet (saat ini, NewValue), nilai nilai harus berbeda dari nilai saat ini. Jika CompareANDSet gagal dieksekusi, Anda hanya dapat mendapatkan kembali nilai nilai, dan terus membandingkan hingga berhasil.
4. Implementasi i ++
Public final int getAndIncrement () {for (;;) {int current = get (); int next = arus + 1; if (compareEndset (saat ini, selanjutnya)) Mengembalikan arus; }}5. Implementasi ++ i
public final int incrementandget () {for (;;) {int current = get (); int next = arus + 1; if (compareEndset (saat ini, selanjutnya)) kembali selanjutnya; }}4. Gunakan contoh atomicinteger
Program berikut menggunakan AtomicInteger untuk mensimulasikan program penjualan tiket. Kedua program tidak akan menjual tiket yang sama dalam hasil berjalan, mereka juga tidak akan menjual tiket sebagai negatif.
package javaleanning;import java.util.concurrent.atomic.AtomicInteger;public class SellTickets {AtomicInteger tickets = new AtomicInteger(100);class Seller implements Runnable{@Override public void run() {while(tickets.get() > 0){int tmp = tickets.get();if(tickets.compareAndSet(tmp, tmp-1)){System.out.println(Thread.currentThread().getName()+" "+tmp);}}}}} public static void main(String[] args) {SellTickets st = new SellTickets();new Thread(st.new Seller(), "Sellera"). Mulai (); utas baru (St.New Seller (), "Sellerb"). Start ();}}5. Masalah ABA
Contoh di atas menjalankan hasil sepenuhnya benar. Ini didasarkan pada fakta bahwa dua (atau lebih) utas beroperasi pada data dalam arah yang sama. Dalam contoh di atas, kedua utas beroperasi pada penurunan tiket. Misalnya, jika beberapa utas melakukan operasi pendaftaran objek pada antrian bersama, maka hasil yang benar dapat diperoleh melalui kelas atomikreferensi (ini sebenarnya adalah kasus untuk antrian yang dipertahankan dalam AQS). Namun, beberapa utas dapat terdaftar atau dihapus, yaitu, arah operasi data tidak konsisten, sehingga ABA dapat terjadi.
Sekarang mari kita ambil contoh yang relatif mudah dipahami untuk menjelaskan masalah ABA. Misalkan ada dua utas T1 dan T2, dan kedua utas ini melakukan operasi penumpukan dan susun pada tumpukan yang sama.
Kami menggunakan ekor yang ditentukan oleh atomicreference untuk menyimpan posisi atas tumpukan
Atomicreference <T> ekor;
Dengan asumsi bahwa utas T1 siap diluncurkan, untuk operasi susun, kita hanya perlu memperbarui posisi teratas tumpukan dari SP ke koran melalui operasi CAS, seperti yang ditunjukkan pada Gambar 1. Namun, sebelum utas T1 mengeksekusi tail. T2 melakukan tiga operasi: A keluar dari tumpukan, B keluar dari tumpukan, dan kemudian A berada di tumpukan. Pada saat ini, sistem mulai menjadwalkan lagi, dan utas T1 terus melakukan operasi penumpukan, tetapi dalam pandangan utas T1, elemen di bagian atas tumpukan masih A (yaitu, T1 masih percaya bahwa B masih merupakan elemen berikutnya, dan soal newse. Pointer dari tumpukan diarahkan ke simpul B. Faktanya, B tidak ada lagi di tumpukan. Hasilnya setelah T1 mengeluarkan tumpukan ditunjukkan pada Gambar 3, yang jelas bukan hasil yang benar.
6. Solusi untuk Masalah ABA
Gunakan Referensi AtomicmarkableRer, Atomicstampedreference. Gunakan dua kelas atom yang disebutkan di atas untuk melakukan operasi. Saat mengimplementasikan instruksi CompareANDSet, mereka tidak hanya perlu membandingkan nilai sebelumnya dan nilai yang diharapkan dari objek, tetapi juga perlu membandingkan nilai perangko (operasi) saat ini dan nilai perangko (operasi) yang diharapkan. Hanya ketika semua hal yang sama benar, metode CompareEndset berhasil. Setiap kali pembaruan berhasil, nilai perangko akan berubah, dan pengaturan nilai perangko dikendalikan oleh programmer sendiri.
public Boolean compareAndSet(V expectedReference, V newReference, int expectedStamp,int newStamp) {Pair<V> current = pair;return expectedReference == current.reference && expectedStamp == current.stamp && ((newReference == current.reference && newStamp == current.stamp) || casPair(current, Pair.of(newReference, newStamp)));}Pada saat ini, metode CompareEndset membutuhkan empat parameter: Referensi yang diharapkan, Newreference, PrecePtamp, Newstamp. Ketika kami menggunakan metode ini, kami harus memastikan bahwa nilai perangko yang diharapkan tidak sama dengan nilai perangko pembaruan. Biasanya newstamp = diharapkan stamp+1
Ambil contoh di atas
Misalkan Thread T1 ada sebelum tumpukan: SP menunjuk ke A dan nilai perangko adalah 100.
Thread T2 mengeksekusi: Setelah A dirilis, SP menunjuk ke B, dan nilai perangko menjadi 101.
Setelah B dirilis, SP menunjuk ke C, dan nilai perangko menjadi 102.
Setelah A dimasukkan ke dalam tumpukan, SP menunjuk ke A dan nilai perangko menjadi 103.
Thread T1 terus menjalankan pernyataan CompareANDSet dan menemukan bahwa meskipun SP masih menunjuk ke A, nilai yang diharapkan dari nilai perangko 100 berbeda dari nilai saat ini 103. Oleh karena itu, CompareANDSet gagal. Anda perlu mendapatkan nilai koran (saat ini, koran akan menunjuk ke C), dan nilai yang diharapkan dari nilai perangko 103, dan kemudian melakukan operasi CompareEndset lagi. Dengan cara ini, yang berhasil meluncurkan tumpukan, SP akan menunjuk ke C.
Perhatikan bahwa karena CompareANDSet hanya dapat mengubah satu nilai pada satu waktu dan tidak dapat mengubah Newreference dan Newstamp pada saat yang sama, selama implementasi, kelas pasangan didefinisikan secara internal untuk mengubah NewReference dan Newstamp menjadi satu objek. Saat melakukan operasi CAS, itu sebenarnya operasi pada objek pasangan.
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); }}Untuk atomicmarkableReRerence, nilai prangko adalah variabel boolean, sedangkan nilai perangko dalam referensi atomicstampedreference adalah variabel integer.
Meringkaskan
Di atas adalah semua tentang diskusi singkat artikel ini tentang prinsip -prinsip implementasi dan aplikasi paket atom di Java. Saya harap ini akan membantu semua orang. Teman yang tertarik dapat terus merujuk ke topik terkait lainnya di situs ini. Jika ada kekurangan, silakan tinggalkan pesan untuk menunjukkannya.