Bagaimana cara kerja atom / volatile / tersinkronisasi secara internal?
Apa perbedaan antara blok kode berikut?
Kode 1
private int counter;
public int getNextUniqueIndex() {
return counter++;
}
Kode 2
private AtomicInteger counter;
public int getNextUniqueIndex() {
return counter.getAndIncrement();
}
Kode 3
private volatile int counter;
public int getNextUniqueIndex() {
return counter++;
}
Apakah volatile
bekerja dengan cara berikut? Adalah
volatile int i = 0;
void incIBy5() {
i += 5;
}
setara dengan
Integer i = 5;
void incIBy5() {
int temp;
synchronized(i) { temp = i }
synchronized(i) { i = temp + 5 }
}
Saya pikir dua utas tidak dapat memasuki blok yang disinkronkan secara bersamaan ... apakah saya benar? Jika ini benar maka bagaimana cara atomic.incrementAndGet()
kerjanya tanpa synchronized
? Dan apakah ini aman?
Dan apa perbedaan antara membaca dan menulis internal dengan variabel volatil / variabel atom? Saya membaca di beberapa artikel bahwa utas memiliki salinan variabel lokal - apa itu?
Jawaban:
Anda secara khusus bertanya tentang bagaimana mereka bekerja secara internal , jadi di sini Anda berada:
Tidak ada sinkronisasi
Ini pada dasarnya membaca nilai dari memori, menambahkannya dan mengembalikannya ke memori. Ini bekerja di utas tunggal tetapi saat ini, di era multi-core, multi-CPU, multi-level cache tidak akan berfungsi dengan benar. Pertama-tama ini memperkenalkan kondisi balapan (beberapa utas dapat membaca nilai pada saat yang sama), tetapi juga masalah visibilitas. Nilai hanya dapat disimpan dalam memori CPU " lokal " (beberapa cache) dan tidak terlihat untuk CPU / core lainnya (dan karenanya - utas). Inilah sebabnya mengapa banyak merujuk pada salinan variabel lokal di suatu utas. Sangat tidak aman. Pertimbangkan kode penghentian utas populer ini:
Tambahkan
volatile
kestopped
variabel dan berfungsi dengan baik - jika ada utas lain yang memodifikasistopped
variabel melaluipleaseStop()
metode, Anda dijamin akan segera melihat perubahan itu dalamwhile(!stopped)
loop utas yang berfungsi . BTW ini juga bukan cara yang baik untuk menghentikan utas, lihat: Cara menghentikan utas yang berjalan selamanya tanpa menggunakan dan Menghentikan utas java tertentu .AtomicInteger
The
AtomicInteger
penggunaan kelas CAS ( membandingkan-dan-swap ) operasi tingkat rendah CPU (tidak ada sinkronisasi diperlukan!) Mereka memungkinkan Anda untuk memodifikasi variabel tertentu hanya jika nilai sekarang sama dengan sesuatu yang lain (dan dikembalikan berhasil). Jadi, ketika Anda menjalankannyagetAndIncrement()
, sebenarnya berjalan dalam satu lingkaran (implementasi nyata yang disederhanakan):Jadi pada dasarnya: baca; mencoba menyimpan nilai yang bertambah; jika tidak berhasil (nilainya tidak lagi sama dengan
current
), baca dan coba lagi. ThecompareAndSet()
diimplementasikan dalam kode asli (assembly).volatile
tanpa sinkronisasiKode ini tidak benar. Ini memperbaiki masalah visibilitas (
volatile
memastikan utas lain dapat melihat perubahan dibuat padacounter
) tetapi masih memiliki kondisi balapan. Ini telah dijelaskan berulang kali: pra / pasca penambahan bukan atom.Satu-satunya efek samping dari cache
volatile
adalah " flushing " sehingga semua pihak lain melihat versi terbaru dari data tersebut. Ini terlalu ketat dalam kebanyakan situasi; itu sebabnyavolatile
tidak standar.volatile
tanpa sinkronisasi (2)Masalah yang sama seperti di atas, tetapi lebih buruk karena
i
tidakprivate
. Kondisi balapan masih ada. Mengapa ini menjadi masalah? Jika, katakanlah, dua utas menjalankan kode ini secara bersamaan, hasilnya mungkin+ 5
atau+ 10
. Namun, Anda dijamin melihat perubahannya.Independen berganda
synchronized
Kejutan, kode ini juga salah. Faktanya, itu sepenuhnya salah. Pertama-tama Anda menyinkronkan
i
, yang akan diubah (apalagi,i
adalah primitif, jadi saya kira Anda menyinkronkan pada sementara yangInteger
dibuat melalui autoboxing ...) Benar-benar cacat. Anda juga bisa menulis:Tidak ada dua utas yang dapat memasuki
synchronized
blok yang sama dengan kunci yang sama . Dalam hal ini (dan juga dalam kode Anda) objek kunci berubah pada setiap eksekusi, jadisynchronized
secara efektif tidak berpengaruh.Bahkan jika Anda telah menggunakan variabel akhir (atau
this
) untuk sinkronisasi, kode tersebut masih salah. Dua thread pertama dapat membacai
untuktemp
serempak (memiliki nilai yang sama secara lokal ditemp
), maka pihak yang ditunjuk pertama nilai baru untuki
(katakanlah, dari 1 sampai 6) dan yang lain melakukan hal yang sama (1-6).Sinkronisasi harus berkisar dari membaca hingga menetapkan nilai. Sinkronisasi pertama Anda tidak memiliki efek (membaca
int
atom adalah) dan yang kedua juga. Menurut pendapat saya, ini adalah bentuk yang benar:sumber
compareAndSet
hanya pembungkus tipis di sekitar operasi CAS. Saya masuk ke beberapa detail dalam jawaban saya.Mendeklarasikan suatu variabel sebagai volatile berarti mengubah nilainya dengan segera memengaruhi penyimpanan memori aktual untuk variabel tersebut. Kompiler tidak dapat mengoptimalkan semua referensi yang dibuat untuk variabel. Ini menjamin bahwa ketika satu utas memodifikasi variabel, semua utas lainnya segera melihat nilai baru. (Ini tidak dijamin untuk variabel non-volatil.)
Mendeklarasikan variabel atom menjamin bahwa operasi yang dilakukan pada variabel terjadi dengan cara atom, yaitu, bahwa semua subteps operasi diselesaikan dalam utas yang dieksekusi dan tidak terganggu oleh utas lainnya. Sebagai contoh, operasi peningkatan-dan-pengujian membutuhkan variabel yang akan bertambah dan kemudian dibandingkan dengan nilai lain; operasi atom menjamin bahwa kedua langkah ini akan diselesaikan seolah-olah itu adalah operasi tunggal yang tidak terpisahkan / tidak terputus.
Menyinkronkan semua akses ke suatu variabel memungkinkan hanya satu utas pada satu waktu untuk mengakses variabel, dan memaksa semua utas lainnya untuk menunggu agar utas yang mengakses melepaskan aksesnya ke variabel.
Akses yang disinkronkan mirip dengan akses atom, tetapi operasi atom umumnya diimplementasikan pada level pemrograman yang lebih rendah. Juga, sangat mungkin untuk menyinkronkan hanya beberapa akses ke suatu variabel dan memungkinkan akses lain untuk tidak disinkronkan (misalnya, menyinkronkan semua penulisan ke variabel tetapi tidak ada yang membaca dari itu).
Atomicity, sinkronisasi, dan volatilitas adalah atribut independen, tetapi biasanya digunakan dalam kombinasi untuk menegakkan kerjasama thread yang tepat untuk mengakses variabel.
Addendum (April 2016)
Akses tersinkronisasi ke variabel biasanya diimplementasikan menggunakan monitor atau semaphore . Ini adalah mekanisme mutex (saling pengecualian) tingkat rendah yang memungkinkan utas untuk mendapatkan kendali atas variabel atau blok kode secara eksklusif, memaksa semua utas lainnya menunggu jika mereka juga berupaya untuk mendapatkan muteks yang sama. Setelah utas memiliki melepaskan mutex, utas lain dapat memperoleh mutex pada gilirannya.
Addendum (Juli 2016)
Sinkronisasi terjadi pada suatu objek . Ini berarti bahwa memanggil metode yang disinkronkan kelas akan mengunci
this
objek panggilan. Metode tersinkronisasi statis akan mengunciClass
objek itu sendiri.Demikian juga, memasuki blok yang disinkronkan memerlukan penguncian
this
objek metode.Ini berarti bahwa metode yang disinkronkan (atau blok) dapat mengeksekusi di beberapa utas pada saat yang sama jika mereka mengunci pada objek yang berbeda , tetapi hanya satu utas yang dapat menjalankan metode yang disinkronkan (atau memblokir) pada suatu waktu untuk objek tunggal tertentu .
sumber
lincah:
volatile
adalah kata kunci.volatile
memaksa semua utas untuk mendapatkan nilai terbaru dari variabel dari memori utama alih-alih dari cache. Tidak diperlukan penguncian untuk mengakses variabel volatil. Semua utas dapat mengakses nilai variabel volatil pada saat yang sama.Menggunakan
volatile
variabel mengurangi risiko kesalahan konsistensi memori, karena setiap penulisan ke variabel volatil membentuk hubungan yang terjadi sebelum dengan membaca selanjutnya dari variabel yang sama.Ini berarti bahwa perubahan pada suatu
volatile
variabel selalu terlihat oleh utas lainnya . Terlebih lagi, ini juga berarti bahwa ketika utas membacavolatile
variabel, ia tidak hanya melihat perubahan terbaru ke volatil, tetapi juga efek samping dari kode yang menyebabkan perubahan .Kapan harus digunakan: Satu utas mengubah data dan utas lainnya harus membaca nilai data terbaru. Utas lain akan mengambil tindakan tetapi tidak memperbarui data .
AtomicXXX:
AtomicXXX
kelas mendukung pemrograman thread-safe bebas kunci pada variabel tunggal.AtomicXXX
Kelas - kelas ini (sepertiAtomicInteger
) menyelesaikan kesalahan inkonsistensi memori / efek samping dari modifikasi variabel volatil, yang telah diakses di banyak utas.Kapan harus menggunakan: Beberapa utas dapat membaca dan mengubah data.
disinkronkan:
synchronized
adalah kata kunci yang digunakan untuk menjaga metode atau blok kode. Dengan membuat metode yang disinkronkan memiliki dua efek:Pertama, tidak mungkin untuk dua pemanggilan
synchronized
metode pada objek yang sama untuk interleave. Ketika satu utas mengeksekusisynchronized
metode untuk objek, semua utas lain yang memanggilsynchronized
metode untuk blok objek yang sama (menunda eksekusi) hingga utas pertama selesai dengan objek.Kedua, ketika suatu
synchronized
metode keluar, itu secara otomatis membangun hubungan yang terjadi sebelum dengan setiap permohonansynchronized
metode untuk objek yang sama. Ini menjamin bahwa perubahan keadaan objek terlihat oleh semua utas.Kapan harus menggunakan: Beberapa utas dapat membaca dan mengubah data. Logika bisnis Anda tidak hanya memperbarui data tetapi juga menjalankan operasi atom
AtomicXXX
setaravolatile + synchronized
meskipun implementasinya berbeda.AmtomicXXX
memperluasvolatile
variabel +compareAndSet
metode tetapi tidak menggunakan sinkronisasi.Pertanyaan SE terkait:
Perbedaan antara volatile dan disinkronkan di Jawa
Volatile boolean vs AtomicBoolean
Artikel bagus untuk dibaca: (Konten di atas diambil dari halaman dokumentasi ini)
https://docs.oracle.com/javase/tutorial/essential/concurrency/sync.html
https://docs.oracle.com/javase/tutorial/essential/concurrency/atomic.html
https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/package-summary.html
sumber
Dua utas tidak dapat memasuki blok yang disinkronkan pada objek yang sama dua kali. Ini berarti bahwa dua utas dapat memasuki blok yang sama pada objek yang berbeda. Kebingungan ini dapat menyebabkan kode seperti ini.
Ini tidak akan berperilaku seperti yang diharapkan karena bisa mengunci objek yang berbeda setiap kali.
Iya. Itu tidak menggunakan penguncian untuk mencapai keamanan benang.
Jika Anda ingin tahu cara kerjanya lebih detail, Anda dapat membaca kode untuk mereka.
Kelas atom menggunakan bidang yang mudah menguap . Tidak ada perbedaan di lapangan. Perbedaannya adalah operasi yang dilakukan. Kelas Atom menggunakan operasi CompareAndSwap atau CAS.
Saya hanya dapat berasumsi bahwa itu merujuk pada fakta bahwa setiap CPU memiliki tampilan cache memori yang berbeda dari setiap CPU lainnya. Untuk memastikan CPU Anda memiliki tampilan data yang konsisten, Anda perlu menggunakan teknik keamanan utas.
Ini hanya masalah ketika memori dibagikan setidaknya satu utas memperbaruinya.
sumber
Vs Atomic Vs Volatile yang Disinkronkan:
Harap perbaiki saya jika ada sesuatu yang saya lewatkan.
sumber
Sinkronisasi + volatile adalah solusi bukti bodoh untuk operasi (pernyataan) menjadi atom sepenuhnya yang mencakup banyak instruksi ke CPU.
Katakan misalnya: volatile int i = 2; i ++, yang tidak lain adalah i = i +1; yang menjadikan saya sebagai nilai 3 di memori setelah eksekusi pernyataan ini. Ini termasuk membaca nilai yang ada dari memori untuk i (yaitu 2), memuat ke register akumulator CPU dan lakukan dengan perhitungan dengan menambah nilai yang ada dengan satu (2 + 1 = 3 dalam akumulator) dan kemudian menulis kembali nilai yang bertambah itu kembali ke memori. Operasi ini tidak cukup atom meskipun nilainya volatile. i menjadi volatile hanya menjamin bahwa TUNGGAL baca / tulis dari memori adalah atom dan tidak dengan GANDA. Oleh karena itu, kita perlu melakukan sinkronisasi juga di sekitar i ++ agar tetap menjadi pernyataan atom yang bodoh. Ingat fakta bahwa suatu pernyataan mencakup banyak pernyataan.
Semoga penjelasannya cukup jelas.
sumber
Java volatile modifier adalah contoh mekanisme khusus untuk menjamin bahwa komunikasi terjadi di antara utas. Ketika satu utas menulis ke variabel volatil, dan utas lainnya melihat tulis itu, utas pertama memberi tahu yang kedua tentang semua isi memori hingga ia melakukan penulisan pada variabel yang tidak stabil tersebut.
Operasi atom dilakukan dalam satu unit tugas tanpa gangguan dari operasi lain. Operasi atom adalah keharusan dalam lingkungan multi-utas untuk menghindari inkonsistensi data.
sumber