Anda secara khusus bertanya tentang bagaimana mereka bekerja secara internal , jadi di sini Anda berada:
Tidak ada sinkronisasi
private int counter;
public int getNextUniqueIndex() {
return counter++;
}
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:
private boolean stopped;
public void run() {
while(!stopped) {
//do some work
}
}
public void pleaseStop() {
stopped = true;
}
Tambahkan volatile
ke stopped
variabel dan berfungsi dengan baik - jika ada utas lain yang memodifikasi stopped
variabel melalui pleaseStop()
metode, Anda dijamin akan segera melihat perubahan itu dalam while(!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
private AtomicInteger counter = new AtomicInteger();
public int getNextUniqueIndex() {
return counter.getAndIncrement();
}
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 menjalankannya getAndIncrement()
, sebenarnya berjalan dalam satu lingkaran (implementasi nyata yang disederhanakan):
int current;
do {
current = get();
} while(!compareAndSet(current, current + 1));
Jadi pada dasarnya: baca; mencoba menyimpan nilai yang bertambah; jika tidak berhasil (nilainya tidak lagi sama dengan current
), baca dan coba lagi. The compareAndSet()
diimplementasikan dalam kode asli (assembly).
volatile
tanpa sinkronisasi
private volatile int counter;
public int getNextUniqueIndex() {
return counter++;
}
Kode ini tidak benar. Ini memperbaiki masalah visibilitas ( volatile
memastikan utas lain dapat melihat perubahan dibuat pada counter
) 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 sebabnya volatile
tidak standar.
volatile
tanpa sinkronisasi (2)
volatile int i = 0;
void incIBy5() {
i += 5;
}
Masalah yang sama seperti di atas, tetapi lebih buruk karena i
tidak private
. 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
void incIBy5() {
int temp;
synchronized(i) { temp = i }
synchronized(i) { i = temp + 5 }
}
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 yang Integer
dibuat melalui autoboxing ...) Benar-benar cacat. Anda juga bisa menulis:
synchronized(new Object()) {
//thread-safe, SRSLy?
}
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 membaca i
untuk temp
serempak (memiliki nilai yang sama secara lokal di temp
), maka pihak yang ditunjuk pertama nilai baru untuk i
(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:
void synchronized incIBy5() {
i += 5
}
void incIBy5() {
synchronized(this) {
i += 5
}
}
void incIBy5() {
synchronized(this) {
int temp = i;
i = temp + 5;
}
}