Özellikle dahili olarak nasıl çalıştıklarını soruyorsunuz , işte buradasınız:
Senkronizasyon yok
private int counter;
public int getNextUniqueIndex() {
return counter++;
}
Temel olarak bellekten değeri okur, arttırır ve belleğe geri koyar. Bu tek bir iş parçacığında çalışır, ancak günümüzde çok çekirdekli, çok işlemcili, çok düzeyli önbellekler çağında doğru çalışmaz. Her şeyden önce, yarış durumu (birkaç iş parçacığı aynı anda değeri okuyabilir), aynı zamanda görünürlük problemlerini de tanıtır. Değer yalnızca " yerel " CPU belleğinde (bazı önbellek) saklanabilir ve diğer CPU'lar / çekirdekler (ve dolayısıyla - iş parçacıkları) için görünmeyebilir. Bu yüzden birçoğu bir iş parçacığında değişkenin yerel kopyasına atıfta bulunur . Çok güvensiz. Bu popüler ama kırık iplik durdurma kodunu düşünün:
private boolean stopped;
public void run() {
while(!stopped) {
//do some work
}
}
public void pleaseStop() {
stopped = true;
}
Ekle volatile
için stopped
değişken ve cezası işleri - Başka iplik değiştirir eğer stopped
aracılığıyla değişken pleaseStop()
yöntemi size parçacığının çalışma anında bu değişikliği görmek için garanti edilir while(!stopped)
döngü. : BTW bu, ya bir iplik kesme görmek için iyi bir yol değildir herhangi kullanmadan sonsuza çalıştıran bir iş parçacığı nasıl engellenir ve belirli java iş parçacığı durduruluyor .
AtomicInteger
private AtomicInteger counter = new AtomicInteger();
public int getNextUniqueIndex() {
return counter.getAndIncrement();
}
AtomicInteger
Sınıf kullandığı CAS ( karşılaştırma ve takas ) alt düzey işlemci operasyonları (hayır senkronizasyon gerekli!) Onlar bugünkü değeri başka bir şeye eşittir (ve başarıyla döndürülür) yalnızca belirli bir değişkeni değiştirmek için izin verir. Yani yürüttüğünüzde getAndIncrement()
aslında bir döngüde çalışır (basitleştirilmiş gerçek uygulama):
int current;
do {
current = get();
} while(!compareAndSet(current, current + 1));
Temel olarak: okumak; artan değeri saklamaya çalışın; başarılı olmazsa (değer artık eşit değildir current
), okuyun ve tekrar deneyin. compareAndSet()
Yerli kod (montaj) uygulanmaktadır.
volatile
senkronizasyon olmadan
private volatile int counter;
public int getNextUniqueIndex() {
return counter++;
}
Bu kod doğru değil. Görünürlük sorununu giderir ( volatile
diğer iş parçacıklarının yapılan değişikliği görebildiğinden emin olur counter
), ancak yine de bir yarış durumu vardır. Bu birçok kez açıklanmıştır : artım öncesi / artım atomik değildir.
Tüm yan tarafların verilerin en yeni sürümünü görmesi için tek yan etkisi volatile
" yıkama " önbellekleri. Bu çoğu durumda çok katıdır; bu yüzden volatile
varsayılan değildir.
volatile
senkronizasyon olmadan (2)
volatile int i = 0;
void incIBy5() {
i += 5;
}
Yukarıdaki ile aynı sorun, ama daha da kötü çünkü i
değil private
. Yarış durumu hala mevcut. O niçin bir problem olsun ki? Örneğin, iki iş parçacığı bu kodu aynı anda çalıştırıyorsa, çıktı + 5
veya olabilir + 10
. Ancak, değişikliği göreceğiniz garanti edilir.
Çoklu bağımsız synchronized
void incIBy5() {
int temp;
synchronized(i) { temp = i }
synchronized(i) { i = temp + 5 }
}
Sürpriz, bu kod da yanlış. Aslında, tamamen yanlış. Öncelikle, i
değişmek üzere olan senkronizasyon yapıyorsunuz (dahası, i
bir ilkel, bu yüzden sanırım Integer
otomatik boksla oluşturulan bir geçici olarak senkronize ediyorsunuz ...) Tamamen kusurlu. Ayrıca şunu da yazabilirsiniz:
synchronized(new Object()) {
//thread-safe, SRSLy?
}
İki kilit parçası aynı synchronized
bloğa aynı bloğa giremez . Bu durumda (ve benzer şekilde kodunuzda) kilit nesnesi her yürütmede değişir, bu nedenle etkili bir etkisi yoktur.synchronized
this
Senkronizasyon için bir son değişken (veya ) kullansanız bile kod hala yanlıştır. İki konu ilk okuyabilir i
için temp
eşzamanlı (yerel aynı değere sahip temp
, ardından ilk atar yeni bir değer) i
(1'den 6'ya kadar diyelim) diğeri (1'den 6'ya kadar) aynı şeyi yapar.
Senkronizasyon, okumadan bir değer atamaya kadar olmalıdır. İlk senkronizasyonunuzun hiçbir etkisi yoktur (a'nın int
atomik olması) ve ikincisinin de etkisi vardır. Bence bunlar doğru biçimlerdir:
void synchronized incIBy5() {
i += 5
}
void incIBy5() {
synchronized(this) {
i += 5
}
}
void incIBy5() {
synchronized(this) {
int temp = i;
i = temp + 5;
}
}