Atomik / uçucu / senkronize arasındaki fark nedir?


298

Atomik / uçucu / senkronize dahili olarak nasıl çalışır?

Aşağıdaki kod blokları arasındaki fark nedir?

Kod 1

private int counter;

public int getNextUniqueIndex() {
    return counter++; 
}

Kod 2

private AtomicInteger counter;

public int getNextUniqueIndex() {
    return counter.getAndIncrement();
}

Kod 3

private volatile int counter;

public int getNextUniqueIndex() {
    return counter++; 
}

Does volatileşu şekilde çalışır? Dır-dir

volatile int i = 0;
void incIBy5() {
    i += 5;
}

eşittir

Integer i = 5;
void incIBy5() {
    int temp;
    synchronized(i) { temp = i }
    synchronized(i) { i = temp + 5 }
}

Bence iki iş parçacığı eşzamanlı bir bloğa aynı anda giremez ... doğru mu? Bu doğruysa, o zaman nasıl atomic.incrementAndGet()çalışır synchronized? Ve iş parçacığı için güvenli mi?

İç okuma ve uçucu değişkenlere / atomik değişkenlere yazma arasındaki fark nedir? Bazı makalede iş parçacığı değişkenlerin yerel bir kopyası olduğunu okudum - bu nedir?


5
Bu, kod bile derlemeyen bir sürü soru yapar. Belki de Uygulamada Java Concurrency gibi iyi bir kitap okumalısınız.
JB Nizet

4
@JBNizet haklısın !!! Bu kitabım var, kısaca Atom kavramı yok ve bazı kavramları alamıyorum. lanet olsun, bu benim hatam değil yazar.
hardik

4
Nasıl uygulandığını gerçekten önemsemeniz gerekmez (ve işletim sistemine göre değişir). Anlamanız gereken sözleşme: değer atomik olarak artırılır ve diğer tüm iş parçacıklarının yeni değeri görmesi garanti edilir.
JB Nizet

Yanıtlar:


392

Ö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 volatileiçin stoppeddeğişken ve cezası işleri - Başka iplik değiştirir eğer stoppedaracı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();
}

AtomicIntegerSı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 ( volatilediğ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 volatilevarsayı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ü ideğ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ı + 5veya 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, ideğişmek üzere olan senkronizasyon yapıyorsunuz (dahası, ibir ilkel, bu yüzden sanırım Integerotomatik 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ı synchronizedbloğ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

thisSenkronizasyon için bir son değişken (veya ) kullansanız bile kod hala yanlıştır. İki konu ilk okuyabilir iiçin tempeş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 intatomik 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;
  }
}

10
Ekleyeceğim tek şey, JVM'nin değişken değerleri üzerlerinde çalışması için kayıtlara kopyalamasıdır. Bu, tek bir CPU / çekirdek üzerinde çalışan evrelerin uçucu olmayan bir değişken için hala farklı değerler görebileceği anlamına gelir.
David Harkness

@ thomasz: karşılaştırmakAndSet (akım, akım + 1) senkronize edilir ?? iki iş parçacığı aynı anda bu yöntemi yürütürken ne olur hayır ??
hardik

@Hardik: compareAndSetCAS operasyonunun etrafında ince bir sargıdır . Cevabımda bazı ayrıntılara giriyorum.
Tomasz Nurkiewicz

1
@thomsasz: tamam, ben bu bağlantı sorusu üzerinden gitmek ve jon skeet tarafından cevap, "iş parçacığı başka bir iş parçacığı yazma gerçekleştirip gerçekleştirmediğini denetlemeden bir değişken değişken okuyamıyor" diyor. ama bir iş parçacığı yazma işlemi ile ikinci iş parçacığı arasında okuyorsa ne olur !! Yanlış mıyım ?? atomik operasyonda ırk durumu değil mi ??
hardik

3
@Hardik: Lütfen sorduğunuz sorulara daha fazla yanıt almak için başka bir soru oluşturun, işte sadece siz ve ben ve yorumlarınız soru sormak için uygun değil. Takip edebilmem için buraya yeni bir soru bağlantısı göndermeyi unutmayın.
Tomasz Nurkiewicz

61

Bir değişkenin uçucu olarak bildirilmesi , değerinin değiştirilmesinin, değişken için gerçek bellek depolamasını hemen etkilediği anlamına gelir. Derleyici, değişkene yapılan referansları optimize edemez. Bu, bir iş parçacığı değişkeni değiştirdiğinde, diğer tüm iş parçacıklarının yeni değeri hemen görmesini sağlar. (Bu, değişken olmayan değişkenler için garanti edilmez.)

Bir bildirme atom operasyon alt adımların hepsi yürütülür parçacığı içinde tamamlanır ve diğer parçacıkları tarafından kesintiye uğramaması değişken üzerinde yapılan işlemler, yani bir atom biçimde meydana geldiğini değişken garantileri. Örneğin, bir artış ve test işlemi değişkenin artırılmasını ve daha sonra başka bir değerle karşılaştırılmasını gerektirir; bir atomik işlem, bu adımların her ikisinin de bölünmez / kesintisiz bir işlemmiş gibi tamamlanacağını garanti eder.

Bir değişkene tüm erişimleri senkronize etmek, değişkene erişmek için aynı anda yalnızca tek bir iş parçacığına izin verir ve diğer tüm iş parçacıklarını, o erişim iş parçacığının değişkene erişimini serbest bırakmasını beklemeye zorlar.

Senkronize erişim atomik erişime benzer, ancak atomik işlemler genellikle daha düşük bir programlama seviyesinde uygulanır. Ayrıca, bir değişkene yalnızca bazı erişimleri senkronize etmek ve diğer erişimlerin senkronize edilmemesine izin vermek tamamen mümkündür (örn. Bir değişkenin tüm yazmalarını senkronize etmek, ancak okumadan hiçbirini senkronize etmek).

Atomisite, senkronizasyon ve uçuculuk bağımsız özelliklerdir, ancak değişkenlere erişmek için uygun iplik işbirliğini sağlamak için tipik olarak kombinasyon halinde kullanılır.

Zeyilname (Nisan 2016)

Bir değişkene senkronize erişim genellikle bir monitör veya semafor kullanılarak gerçekleştirilir . Bunlar, bir iş parçacığının münhasıran bir değişkenin veya kod bloğunun kontrolünü elde etmesini sağlayan ve diğer tüm evreleri aynı muteksi edinmeye çalışırlarsa beklemeye zorlayan düşük seviyeli muteks (karşılıklı dışlama) mekanizmalarıdır. Sahip olan iş parçacığı muteksi serbest bıraktığında, başka bir iş parçacığı da muteksi sırayla alabilir.

Zeyilname (Temmuz 2016)

Eşitleme bir nesnede gerçekleşir . Bu, sınıfın senkronize bir yönteminin çağrılmasının thisçağrının nesnesini kilitleyeceği anlamına gelir . Statik senkronize yöntemler Classnesnenin kendisini kilitler .

Benzer şekilde, senkronize bir bloğun girilmesi this, yöntemin nesnesinin kilitlenmesini gerektirir .

Bu, farklı nesneler üzerinde kilitleniyorsa, senkronize edilmiş bir yöntemin (veya bloğun) aynı anda birden çok iş parçacığında yürütülebileceği anlamına gelir , ancak belirli bir tek nesne için aynı anda yalnızca bir iş parçacığı senkronize edilmiş bir yöntem (veya blok) yürütebilir .


25

uçucu:

volatilebir anahtar kelimedir. volatiletüm iş parçacıklarını önbellek yerine ana bellekten değişkenin en son değerini almaya zorlar. Uçucu değişkenlere erişmek için kilitleme gerekmez. Tüm evreler, değişken değişken değerine aynı anda erişebilir.

volatileDeğişkenlerin kullanılması bellek tutarlılığı hataları riskini azaltır, çünkü uçucu bir değişkene yapılan herhangi bir yazma aynı değişkenin sonraki okumalarıyla daha önce gerçekleşen bir ilişki kurar.

Bu, bir volatiledeğişkende yapılan değişikliklerin her zaman diğer iş parçacıkları tarafından görülebilir olduğu anlamına gelir . Dahası, aynı zamanda , bir iş parçacığı bir volatiledeğişkeni okuduğunda , sadece uçucudaki en son değişikliği değil, aynı zamanda değişikliği sağlayan kodun yan etkilerini de görür .

Ne zaman kullanılır: Bir iş parçacığı verileri değiştirir ve diğer iş parçacıkları en son veri değerini okumak zorundadır. Diğer iş parçacıkları biraz işlem yapar ancak verileri güncellemez .

AtomicXXX:

AtomicXXXsınıfları, tek değişkenler üzerinde kilitsiz, iş parçacığı için güvenli programlamayı destekler. Bu AtomicXXXsınıflar (gibi AtomicInteger), birden çok iş parçacığında erişilen uçucu değişkenlerin modifikasyonunun bellek tutarsızlık hatalarını / yan etkilerini giderir.

Ne zaman kullanılır: Birden çok iş parçacığı verileri okuyabilir ve değiştirebilir.

senkronize:

synchronizedbir yöntemi veya kod bloğunu korumak için kullanılan anahtar kelimedir. Yöntemi senkronize hale getirmenin iki etkisi vardır:

  1. Birincisi, synchronizedaynı nesne üzerindeki iki yöntem çağrısının serpiştirilmesi mümkün değildir . Bir iş parçacığı synchronizedbir nesne için bir yöntem yürütürken synchronized, ilk iş parçacığı nesne ile yapılana kadar aynı nesne bloğu için yöntemleri çağıran (yürütmeyi askıya alma) diğer tüm iş parçacıkları.

  2. İkincisi, bir synchronizedyöntemden çıkıldığında, synchronizedaynı nesne için bir yöntemin daha sonraki herhangi bir çağrılmasıyla otomatik olarak gerçekleşmeden önce bir ilişki kurar . Bu, nesnenin durumundaki değişikliklerin tüm iş parçacıkları tarafından görülebilir olmasını sağlar.

Ne zaman kullanılır: Birden çok iş parçacığı verileri okuyabilir ve değiştirebilir. İş mantığınız sadece verileri güncellemekle kalmaz, aynı zamanda atomik işlemleri de yürütür

AtomicXXXvolatile + synchronizeduygulama farklı olmasına rağmen eşdeğerdir . değişkenleri + yöntemleri AmtomicXXXgenişletir ancak senkronizasyon kullanmaz.volatilecompareAndSet

İlgili SE soruları:

Java'da uçucu ve senkronize arasındaki fark

Uçucu boolean vs AtomicBoolean

Okunacak iyi makaleler: (Yukarıdaki içerik bu dokümantasyon sayfalarından alınmıştır)

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


2
Bu, kod yürütmeyi gerçekten nasıl etkilediğini anlamada önemli olan, açıklanan anahtar kelimelerin / özelliklerin gerçek anlamlarından önce söz ettikleri ilk cevaptır. Yüksek oylanan cevaplar bu yönü kaçırmaktadır.
jhyot

5

İki iş parçacığının aynı anda Eşitle bloğuna giremediğini biliyorum

İki iş parçacığı aynı nesneye eşitlenmiş bir bloğa iki kez giremez. Bu, iki iş parçacığının farklı nesnelere aynı bloğa girebileceği anlamına gelir. Bu karışıklık böyle bir koda yol açabilir.

private Integer i = 0;

synchronized(i) {
   i++;
}

Bu, her seferinde farklı bir nesneye kilitlenebileceği için beklendiği gibi davranmayacaktır.

bu doğruysa bu atomic.incrementAndGet () nasıl Synchronize olmadan çalışır ?? ve iplik güvenli ??

Evet. İplik güvenliğini sağlamak için kilitleme kullanmaz.

Nasıl daha ayrıntılı çalıştıklarını öğrenmek istiyorsanız, onlar için kodu okuyabilirsiniz.

İçsel okuma ve Uçucu Değişken / Atomik Değişken'e yazma arasındaki fark nedir?

Atom sınıfı uçucu alanlar kullanır . Alanda hiçbir fark yoktur. Fark, gerçekleştirilen işlemlerdir. Atom sınıfları CompareAndSwap veya CAS işlemlerini kullanır.

i iş parçacığı değişkenlerin yerel kopyası olan bu ne okumak ??

Sadece her CPU'nun diğer CPU'lardan farklı olabilen kendi önbellek görünümüne sahip olduğunu varsayabilirim. CPU'nuzun verileri tutarlı bir şekilde görmesini sağlamak için evre güvenliği tekniklerini kullanmanız gerekir.

Bu, yalnızca en az bir iş parçacığı bellek paylaşıldığında bir konuyu günceller.


@Aniket Thakur Bundan emin misin? Tamsayı değişmez. Yani i ++ muhtemelen int değerini otomatik olarak açacak, artıracak ve daha önce olduğu gibi olmayan yeni bir Tamsayı oluşturacaktır. İ nihai yapmayı deneyin ve i ++ çağrıldığında derleyici hataları alırsınız.
fuemf5

2

Senkronize Vs Atomik Vs Uçucu:

  • Uçucu ve Atomik yalnızca değişkene uygulanırken, Senkronize yöntemde uygulanır.
  • Uçucu görünürlük sağlar, nesnenin atomisitesi / kıvamı değil, diğer ikisi de görünürlük ve atomisite sağlar.
  • RAM'de geçici değişken depo ve erişim daha hızlıdır, ancak senkronize edilmiş anahtar kelime olmadan İş parçacığı güvenliğini veya senkronizasyonunu sağlayamayız.
  • Senkronize edilmiş senkronize blok veya senkronize yöntem olarak uygulanmış olsa da her ikisi de değildir. Her ikisinde de aynı şeyi yapamadığımız zaman, senkronize edilmiş anahtar kelime yardımıyla güvenli çoklu kod satırını işleyebiliriz.
  • Senkronize aynı sınıf nesnesini veya farklı sınıf nesnesini kilitleyebilir, ancak ikisi de yapamaz.

Kaçırdığım bir şey varsa lütfen beni düzeltin.


1

Bir uçucu + senkronizasyon, bir işlemin (ifadenin) tamamen atomik olması için CPU'ya birden fazla talimat içeren kusursuz bir çözümdür.

Örneğin diyelim: uçucu int i = 2; i = i + 1'den başka bir şey olmayan i ++; Bu da i bu ifadenin yürütülmesinden sonra bellekte 3 değeri olarak yapar. Bu, i için bellekten mevcut değerin okunması (2'dir), CPU akümülatör kaydına yüklenir ve mevcut değeri birle (akümülatörde 2 + 1 = 3) artırarak hesaplamayı yapar ve daha sonra bu artan değeri tekrar yazmayı içerir geri belleğe. Bu işlemler yeterince atomik olmasa da i değeri değişkendir. i uçucu olmak sadece bir TEK okuma / yazma bellekten atomik değil, MULTIPLE ile garanti eder. Bu nedenle, aptal geçirmez atomik ifade olmasını sağlamak için i ++ etrafında da senkronize olmamız gerekir. Bir ifadenin birden fazla ifade içerdiğini unutmayın.

Umarım açıklama yeterince açıktır.


1

Java uçucu değiştiricisi, iş parçacıkları arasında iletişimin sağlanmasını garanti eden özel bir mekanizmaya örnektir. Bir iş parçacığı, değişken bir değişkene yazdığında ve başka bir iş parçacığı bu yazmayı gördüğünde, ilk iş parçacığı, ikincisini, bu değişken değişkenine yazma gerçekleştirene kadar bellek içeriğinin tamamını anlatır.

Atom işlemleri , diğer işlemlerden etkilenmeden tek bir görev biriminde gerçekleştirilir. Atomik işlemler, veri tutarsızlığını önlemek için çok iş parçacıklı ortamda gereklidir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.