Penting untuk dipahami bahwa ada dua aspek untuk keselamatan ulir.
- kontrol pelaksanaan, dan
- visibilitas memori
Yang pertama berkaitan dengan pengontrolan ketika kode dijalankan (termasuk urutan instruksi dijalankan) dan apakah dapat dieksekusi secara bersamaan, dan yang kedua berkaitan dengan ketika efek dalam memori dari apa yang telah dilakukan dapat dilihat oleh utas lainnya. Karena setiap CPU memiliki beberapa tingkat cache antara itu dan memori utama, utas yang berjalan pada CPU atau inti yang berbeda dapat melihat "memori" secara berbeda pada setiap waktu tertentu karena utas diizinkan untuk mendapatkan dan bekerja pada salinan pribadi dari memori utama.
Penggunaan synchronized
mencegah utas lainnya mendapatkan monitor (atau kunci) untuk objek yang sama , sehingga mencegah semua blok kode yang dilindungi oleh sinkronisasi pada objek yang sama dari mengeksekusi secara bersamaan. Sinkronisasi juga menciptakan penghalang memori "terjadi-sebelum", menyebabkan batasan visibilitas memori sehingga apa pun yang dilakukan sampai titik beberapa thread melepaskan kunci muncul ke thread lain kemudian mendapatkan kunci yang sama dengan yang terjadi sebelum memperoleh kunci. Dalam istilah praktis, pada perangkat keras saat ini, ini biasanya menyebabkan pembilasan cache CPU ketika monitor diperoleh dan menulis ke memori utama ketika dirilis, keduanya (relatif) mahal.
Menggunakan volatile
, di sisi lain, pasukan semua akses (membaca atau menulis) dengan variabel yang mudah menguap terjadi ke memori utama, efektif menjaga variabel yang mudah menguap dari cache CPU. Ini dapat berguna untuk beberapa tindakan di mana hanya diperlukan agar visibilitas variabel menjadi benar dan urutan akses tidak penting. Menggunakan volatile
juga mengubah perawatan long
dan double
membutuhkan akses ke mereka untuk menjadi atom; pada beberapa perangkat keras (lama) ini mungkin memerlukan kunci, meskipun tidak pada perangkat keras 64 bit modern. Di bawah model memori baru (JSR-133) untuk Java 5+, semantik volatile telah diperkuat menjadi hampir sekuat yang disinkronkan sehubungan dengan visibilitas memori dan pemesanan instruksi (lihat http://www.cs.umd.edu /users/pugh/java/memoryModel/jsr-133-faq.html#volatile). Untuk tujuan visibilitas, setiap akses ke bidang yang mudah menguap bertindak seperti setengah sinkronisasi.
Di bawah model memori baru, masih benar bahwa variabel volatil tidak dapat disusun ulang satu sama lain. Perbedaannya adalah bahwa sekarang tidak lagi begitu mudah untuk menyusun ulang akses bidang normal di sekitar mereka. Menulis ke bidang yang mudah menguap memiliki efek memori yang sama dengan rilis monitor, dan membaca dari bidang yang mudah menguap memiliki efek memori yang sama dengan yang diperoleh monitor. Akibatnya, karena model memori baru menempatkan batasan yang lebih ketat pada pengurutan ulang akses bidang volatil dengan akses bidang lainnya, volatil atau tidak, apa pun yang terlihat berulir A
ketika menulis ke bidang volatil f
menjadi terlihat berulir B
ketika dibaca f
.
- JSR 133 (Model Memori Java) FAQ
Jadi, sekarang kedua bentuk penghalang memori (di bawah JMM saat ini) menyebabkan penghalang pemesanan ulang instruksi yang mencegah kompiler atau run-time dari memesan ulang instruksi di penghalang. Di JMM lama, volatile tidak mencegah pemesanan ulang. Ini bisa menjadi penting, karena selain dari hambatan ingatan, satu-satunya batasan yang diberlakukan adalah bahwa, untuk utas tertentu , efek bersih dari kode adalah sama seperti jadinya jika instruksi dieksekusi tepat sesuai urutan di mana mereka muncul di sumber.
Salah satu penggunaan volatile adalah untuk objek yang dibagikan tetapi tidak dapat diubah diciptakan kembali dengan cepat, dengan banyak utas lainnya mengambil referensi ke objek tersebut pada titik tertentu dalam siklus eksekusi mereka. Satu membutuhkan utas lainnya untuk mulai menggunakan objek yang dibuat kembali setelah dipublikasikan, tetapi tidak memerlukan overhead tambahan sinkronisasi penuh dan pertikaian yang menyertainya dan pembilasan cache.
// Declaration
public class SharedLocation {
static public SomeObject someObject=new SomeObject(); // default object
}
// Publishing code
// Note: do not simply use SharedLocation.someObject.xxx(), since although
// someObject will be internally consistent for xxx(), a subsequent
// call to yyy() might be inconsistent with xxx() if the object was
// replaced in between calls.
SharedLocation.someObject=new SomeObject(...); // new object is published
// Using code
private String getError() {
SomeObject myCopy=SharedLocation.someObject; // gets current copy
...
int cod=myCopy.getErrorCode();
String txt=myCopy.getErrorText();
return (cod+" - "+txt);
}
// And so on, with myCopy always in a consistent state within and across calls
// Eventually we will return to the code that gets the current SomeObject.
Berbicara kepada pertanyaan baca-perbarui-tulis Anda, secara khusus. Pertimbangkan kode tidak aman berikut:
public void updateCounter() {
if(counter==1000) { counter=0; }
else { counter++; }
}
Sekarang, dengan metode updateCounter () tidak disinkronkan, dua utas dapat memasukkannya secara bersamaan. Di antara banyak permutasi dari apa yang bisa terjadi, satu adalah bahwa thread-1 melakukan tes untuk penghitung == 1000 dan menemukan itu benar dan kemudian ditangguhkan. Kemudian thread-2 melakukan tes yang sama dan juga melihatnya benar dan ditangguhkan. Kemudian utas-1 melanjutkan dan menetapkan penghitung ke 0. Kemudian utas-2 melanjutkan dan kembali menetapkan pencacah ke 0 karena melewatkan pembaruan dari utas-1. Ini juga dapat terjadi bahkan jika penggantian ulir tidak terjadi seperti yang saya jelaskan, tetapi hanya karena dua salinan cache yang berbeda hadir dalam dua inti CPU yang berbeda dan masing-masing ulir berjalan pada inti yang terpisah. Dalam hal ini, satu utas dapat memiliki penghitung pada satu nilai dan yang lain dapat memiliki penghitung pada nilai yang sama sekali berbeda hanya karena caching.
Apa yang penting dalam contoh ini adalah bahwa penghitung variabel dibaca dari memori utama ke dalam cache, diperbarui dalam cache dan hanya ditulis kembali ke memori utama di beberapa titik tak tentu kemudian ketika penghalang memori terjadi atau ketika memori cache diperlukan untuk hal lain. Membuat penghitung volatile
tidak mencukupi untuk keamanan thread kode ini, karena pengujian untuk maksimum dan penugasan adalah operasi terpisah, termasuk kenaikan yang merupakan serangkaian read+increment+write
instruksi mesin non-atom , seperti:
MOV EAX,counter
INC EAX
MOV counter,EAX
Variabel volatil hanya berguna ketika semua operasi yang dilakukan adalah "atom", seperti contoh saya di mana referensi ke objek yang sepenuhnya terbentuk hanya dibaca atau ditulis (dan, tentu saja, biasanya hanya ditulis dari satu titik). Contoh lain adalah referensi array yang mudah menguap yang mendukung daftar copy-on-write, asalkan array hanya dibaca dengan terlebih dahulu mengambil salinan referensi lokal ke sana.
http://javaexp.blogspot.com/2007/12/difference-between-volatile-and.html
sumber
synchronized
adalah pengubah batasan akses tingkat metode / blok. Ini akan memastikan bahwa satu utas memiliki kunci untuk bagian kritis. Hanya utas, yang memiliki kunci yang dapat memasukisynchronized
blok. Jika utas lain mencoba mengakses bagian kritis ini, mereka harus menunggu sampai pemilik saat ini melepaskan kunci.volatile
adalah pengubah akses variabel yang memaksa semua utas untuk mendapatkan nilai terbaru dari variabel dari memori utama. Tidak diperlukan penguncian untuk mengaksesvolatile
variabel. Semua utas dapat mengakses nilai variabel volatil pada saat yang sama.Contoh yang baik untuk menggunakan variabel volatile:
Date
variabel.Asumsikan bahwa Anda telah membuat variabel Tanggal
volatile
. Semua utas, yang mengakses variabel ini selalu mendapatkan data terbaru dari memori utama sehingga semua utas menunjukkan nilai Tanggal nyata (aktual). Anda tidak perlu utas berbeda yang menunjukkan waktu berbeda untuk variabel yang sama. Semua utas harus menunjukkan nilai Tanggal yang benar.Lihat artikel ini untuk pemahaman
volatile
konsep yang lebih baik .Lawrence Dol cleary menjelaskan
read-write-update query
.Mengenai pertanyaan Anda yang lain
Anda harus menggunakan
volatile
jika Anda berpikir semua utas harus mendapatkan nilai aktual dari variabel secara real time seperti contoh yang saya jelaskan untuk variabel Tanggal.Jawabannya akan sama dengan di kueri pertama.
Lihat artikel ini untuk pemahaman yang lebih baik.
sumber
tl; dr :
Ada 3 masalah utama dengan multithreading:
1) Kondisi Balapan
2) Memori cache / basi
3) Pengoptimal dan pengoptimalan CPU
volatile
dapat menyelesaikan 2 & 3, tetapi tidak dapat menyelesaikan 1.synchronized
/ kunci eksplisit dapat menyelesaikan 1, 2 & 3.Elaborasi :
1) Pertimbangkan utas ini kode tidak aman:
x++;
Walaupun terlihat seperti satu operasi, sebenarnya 3: membaca nilai x saat ini dari memori, menambahkan 1 ke dalamnya, dan menyimpannya kembali ke memori. Jika beberapa utas mencoba melakukannya secara bersamaan, hasil operasi tidak ditentukan. Jika
x
awalnya adalah 1, setelah 2 utas yang mengoperasikan kode itu mungkin 2 dan mungkin 3, tergantung pada utas yang menyelesaikan bagian mana dari operasi sebelum kontrol dipindahkan ke utas lainnya. Ini adalah bentuk kondisi balapan .Menggunakan
synchronized
pada blok kode membuatnya atomik - artinya membuatnya seolah-olah 3 operasi terjadi sekaligus, dan tidak ada cara untuk utas lain untuk datang di tengah dan mengganggu. Jadi jikax
dulu 1, dan 2 utas mencoba untuk membentuk sebelumnyax++
kita tahu pada akhirnya itu akan sama dengan 3. Jadi itu memecahkan masalah kondisi balapan.Menandai
x
sebagaivolatile
tidak membuatx++;
atom, jadi itu tidak menyelesaikan masalah ini.2) Selain itu, utas memiliki konteksnya sendiri - yaitu mereka dapat menyimpan nilai dari memori utama. Itu berarti bahwa beberapa utas dapat memiliki salinan variabel, tetapi mereka beroperasi pada copy pekerjaan mereka tanpa berbagi keadaan variabel baru di antara utas lainnya.
Pertimbangkan itu di satu utas
x = 10;
,. Dan agak kemudian, di utas lainnyax = 20;
,. Perubahan nilaix
mungkin tidak muncul di utas pertama, karena utas lain telah menyimpan nilai baru ke memori yang berfungsi, tetapi belum menyalinnya ke memori utama. Atau itu memang menyalinnya ke memori utama, tetapi utas pertama belum memperbarui salinan yang berfungsi. Jadi jika sekarang utas pertama memeriksaif (x == 20)
jawabannya akanfalse
.Menandai variabel sebagai
volatile
dasarnya memberitahu semua utas untuk melakukan operasi baca dan tulis pada memori utama saja.synchronized
memberitahu setiap utas untuk memperbarui nilai mereka dari memori utama ketika mereka memasuki blok, dan membuang hasilnya kembali ke memori utama ketika mereka keluar dari blok.Perhatikan bahwa tidak seperti perlombaan data, memori basi tidak mudah (kembali) diproduksi, karena bagaimanapun memori flush ke memori utama terjadi.
3) Pengompilasi dan CPU dapat (tanpa segala bentuk sinkronisasi antara utas) memperlakukan semua kode sebagai utas tunggal. Artinya dapat melihat beberapa kode, yang sangat berarti dalam aspek multithreading, dan memperlakukannya seolah-olah itu adalah utas tunggal, di mana itu tidak begitu berarti. Jadi dapat melihat kode dan memutuskan, demi optimasi, untuk menyusun ulang, atau bahkan menghapus bagian dari itu sepenuhnya, jika tidak tahu bahwa kode ini dirancang untuk bekerja pada banyak utas.
Pertimbangkan kode berikut:
Anda akan berpikir bahwa threadB hanya dapat mencetak 20 (atau tidak mencetak apa-apa sama sekali jika threadB if-check dieksekusi sebelum pengaturan
b
ke true), sepertib
yang disetel ke true hanya setelahx
diatur ke 20, tetapi kompiler / CPU mungkin memutuskan untuk memesan ulang threadA, dalam hal ini threadB juga dapat mencetak 10. Menandaib
sebagaivolatile
memastikan bahwa itu tidak akan disusun ulang (atau dibuang dalam kasus-kasus tertentu). Yang berarti threadB hanya bisa mencetak 20 (atau tidak sama sekali). Menandai metode yang disinkronkan akan mencapai hasil yang sama. Juga menandai variabel sebagaivolatile
hanya memastikan bahwa itu tidak akan disusun ulang, tetapi segala sesuatu sebelum / setelah itu masih dapat disusun ulang, sehingga sinkronisasi dapat lebih cocok dalam beberapa skenario.Perhatikan bahwa sebelum Java 5 New Memory Model, volatile tidak menyelesaikan masalah ini.
sumber
INC
operasi Majelis tunggal , operasi CPU yang mendasarinya masih 3 kali lipat dan membutuhkan penguncian untuk keamanan thread. Poin bagus. Meskipun,INC/DEC
perintah dapat ditandai secara atom dalam perakitan dan masih menjadi 1 operasi atom.