AtomicInteger için pratik kullanımlar


230

AtomicInteger ve diğer Atomik değişkenlerin eşzamanlı erişime izin verdiğini anlıyorum. Hangi durumlarda bu sınıf tipik olarak kullanılır?

Yanıtlar:


190

Bunun iki ana kullanımı vardır AtomicInteger:

  • incrementAndGet()Birçok iplik tarafından aynı anda kullanılabilen bir atom sayacı ( vb.)

  • Engellemeyen algoritmaları uygulamak için karşılaştırma ve takas talimatını ( compareAndSet()) destekleyen bir ilkel olarak .

    İşte Brian Göetz'in Uygulamadaki Java Eşzamanlılığı'ndan engellenmeyen rastgele sayı üretecinin bir örneği :

    public class AtomicPseudoRandom extends PseudoRandom {
        private AtomicInteger seed;
        AtomicPseudoRandom(int seed) {
            this.seed = new AtomicInteger(seed);
        }
    
        public int nextInt(int n) {
            while (true) {
                int s = seed.get();
                int nextSeed = calculateNext(s);
                if (seed.compareAndSet(s, nextSeed)) {
                    int remainder = s % n;
                    return remainder > 0 ? remainder : remainder + n;
                }
            }
        }
        ...
    }

    Gördüğünüz gibi, temel olarak neredeyse aynı şekilde çalışır incrementAndGet(), ancak calculateNext()artış yerine rastgele hesaplama ( ) gerçekleştirir (ve dönüşten önce sonucu işler).


1
Sanırım ilk kullanımı anlıyorum. Bu, bir özniteliğe tekrar erişilmeden önce sayacın artırıldığından emin olmak içindir. Doğru? İkinci kullanım için kısa bir örnek verebilir misiniz?
James P.

8
İlk kullanımla ilgili anlayışınız doğrudur - başka bir iş parçacığı readve write that value + 1işlemleri arasındaki sayacı değiştirirse , eski güncelleştirmenin üzerine yazmak yerine ("kayıp güncelleme" sorunundan kaçınmak) bunun algılanmasını sağlar. Bu aslında compareAndSet- eski değer olsaydı, 2sınıf gerçekten çağırırsa compareAndSet(2, 3)- özel bir durumdur , bu nedenle başka bir iş parçacığı bu arada değeri değiştirdiyse, arttırma yöntemi etkili bir şekilde baştan başlar.
Andrzej Doyle

3
"kalan> 0? kalan: kalan + n;" bu ifadede, 0 olduğunda n'ye kalan eklemek için bir neden var mı?
sandeepkunkunuru

101

Düşünebileceğim en basit örnek, artırmayı atomik bir işlem yapmaktır.

Standart girişlerle:

private volatile int counter;

public int getNextUniqueIndex() {
    return counter++; // Not atomic, multiple threads could get the same result
}

AtomicInteger ile:

private AtomicInteger counter;

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

İkincisi, tüm erişimi senkronize etmek zorunda kalmadan basit mutasyon efektleri (özellikle sayma veya benzersiz indeksleme) gerçekleştirmenin çok basit bir yoludur.

Daha karmaşık bir senkronizasyon içermeyen mantık, compareAndSet()bir iyimser kilitleme tipi olarak kullanılabilir - geçerli değeri alın, buna göre hesaplama sonucunu ayarlayın, bu sonucu ayarlayın iff değeri hala hesaplama yapmak için kullanılan giriştir , başka bir şekilde yeniden başlayın - ancak sayma örnekleri çok faydalıdır ve AtomicIntegersdahil olan birden fazla iş parçacığı ipucu varsa sayma ve VM çapında benzersiz jeneratörler için sık sık kullanacağım , çünkü çalışmak çok kolay çünkü neredeyse düz kullanmak için erken optimizasyonu düşünürdüm ints.

Hemen hemen her zaman aynı senkronizasyon garantilerini intsve uygun synchronizedbeyanları elde edebilmenize rağmen , güzelliği AtomicInteger, iplik güvenliğinin, her yöntemin olası araya ekleme ve tutulan monitörleri hakkında endişelenmeniz yerine, gerçek nesnenin kendisine yerleşik olmasıdır. bu intdeğere erişmek için olur . Arama getAndIncrement()sırasında iş parçacığı güvenliğini yanlışlıkla doğru şekilde ihlal etmek , geri dönüp i++hatırlamaktan (ya da hatırlamamaktan) daha önce doğru monitör setini elde etmekten çok daha zordur .


2
Bu açık açıklama için teşekkürler. AtomicInteger kullanmanın, yöntemlerin senkronize edildiği bir sınıfa göre avantajları nelerdir? İkincisi "daha ağır" olarak kabul edilir mi?
James P.

3
Benim bakış açımdan, bu AtomicIntegers ile elde ettiğiniz kapsüllemedir - senkronizasyon tam olarak ihtiyacınız olan şeyde gerçekleşir ve amaçlanan sonucun ne olduğunu açıklamak için genel API'da bulunan açıklayıcı yöntemler elde edersiniz. (Artı bir dereceye kadar haklısınız, çoğu zaman bir sınıftaki tüm yöntemleri basitçe çok kaba bir şekilde senkronize eder, ancak HotSpot kilit optimizasyonları ve erken optimizasyona karşı kurallar yaparken, okunabilirliğin bir performanstan daha fazla fayda sağlar.)
Andrzej Doyle

Bu çok açık ve kesin bir açıklama, teşekkürler !!
Akash5288

Sonunda benim için düzgün bir şekilde temizleyen bir açıklama.
Benny Bottema

58

AtomicInteger'in sahip olduğu yöntemlere bakarsanız, bunların ints'deki genel işlemlere karşılık gelme eğiliminde olduğunu fark edeceksiniz. Örneğin:

static AtomicInteger i;

// Later, in a thread
int current = i.incrementAndGet();

bunun iş parçacığı için güvenli sürümüdür:

static int i;

// Later, in a thread
int current = ++i;

Yöntemler böyle haritasına:
++iedilir i.incrementAndGet()
i++olduğu i.getAndIncrement()
--iolduğu i.decrementAndGet()
i--olduğu i.getAndDecrement()
i = xolduğu i.set(x)
x = iedilmektedirx = i.get()

Diğer kolaylık yöntemleri yanı vardır gibi compareAndSetyaaddAndGet


37

Birincil kullanımı, AtomicIntegerçok iş parçacıklı bir bağlamda olduğunuzda ve kullanmadan bir tamsayı üzerinde iş parçacığı güvenli işlemleri yapmanız gerektiğidir synchronized. İlkel tipte atama ve erişim intzaten atomiktir, ancak atomik AtomicIntegerolmayan birçok işlemle birlikte gelir int.

En basit olanı getAndXXXveya xXXAndGet. Örneğin getAndIncrement(), atomik i++olmayan bir atomik eşdeğerdir, çünkü aslında üç işlem için bir kısayoldur: geri alma, toplama ve atama. compareAndSetsemafor, kilit, mandal vb. uygulamalarda çok faydalıdır.

Bunu kullanmak, AtomicIntegersenkronizasyonu kullanarak aynı işlemi yapmaktan daha hızlı ve daha okunaklıdır.

Basit bir test:

public synchronized int incrementNotAtomic() {
    return notAtomic++;
}

public void performTestNotAtomic() {
    final long start = System.currentTimeMillis();
    for (int i = 0 ; i < NUM ; i++) {
        incrementNotAtomic();
    }
    System.out.println("Not atomic: "+(System.currentTimeMillis() - start));
}

public void performTestAtomic() {
    final long start = System.currentTimeMillis();
    for (int i = 0 ; i < NUM ; i++) {
        atomic.getAndIncrement();
    }
    System.out.println("Atomic: "+(System.currentTimeMillis() - start));
}

Java 1.6'lı bilgisayarımda, atomik test 3 saniye içinde, senkronize edilmiş cihaz ise yaklaşık 5.5 saniye içinde çalışıyor. Buradaki problem, senkronize etme ( notAtomic++) işleminin gerçekten kısa olmasıdır. Dolayısıyla senkronizasyonun maliyeti operasyona göre gerçekten önemlidir.

Atomikliğin yanında AtomicInteger, Integerörneğin Mapdeğerler olarak değişken bir versiyonu olarak kullanılabilir.


1
AtomicIntegerBir harita anahtarı olarak kullanmak istediğimi düşünmüyorum , çünkü varsayılan equals()uygulamayı kullanıyor , ki bu kesinlikle bir haritada kullanıldığında anlambilimin olmasını beklediğiniz şey değil.
Andrzej Doyle

1
@Andrzej emin, değişmez olması gereken anahtar olarak değil, bir değer.
gabuzo

@gabuzo Atomik tamsayının neden senkronize edilmiş performans gösterdiği hakkında bir fikriniz var mı?
Supun Wijerathne

Test şu anda oldukça eski (6 yıldan fazla) yeni bir JRE ile tekrar test etmek ilginç olabilir. AtomicInteger'da cevap verebilecek kadar derin gitmedim, ancak bu çok özel bir görev olduğu için sadece bu özel durumda çalışan senkronizasyon tekniklerini kullanacak. Ayrıca, testin tek kullanımlık olduğunu ve ağır yüklü bir ortamda benzer bir test yapmanın AtomicInteger için böyle açık bir zafer vermeyebileceğini unutmayın
gabuzo

İnanıyorum onun 3 ms ve 5.5 ms
Sathesh

18

Örneğin, bazı sınıf örnekleri üreten bir kütüphane var. Bu örneklerin her biri benzersiz bir tamsayı kimliğine sahip olmalıdır, çünkü bu örnekler bir sunucuya gönderilen komutları temsil eder ve her komutun benzersiz bir kimliği olmalıdır. Birden çok iş parçacığının aynı anda komut göndermesine izin verildiğinden, bu kimlikleri oluşturmak için bir AtomicInteger kullanıyorum. Alternatif bir yaklaşım, bir çeşit kilit ve normal bir tam sayı kullanmak olabilir, ancak bu hem daha yavaş hem de daha az zariftir.


Bu pratik örneği paylaştığınız için teşekkür ederiz. Programıma aktardığım her dosya için benzersiz bir kimliğe ihtiyacım olduğu için kullanmam gereken bir şey gibi geliyor :)
James P.

7

Gabuzo'nun dediği gibi, bazen int ile referansta geçmek istediğimde AtomicIntegers kullanıyorum. Mimariye özgü kodu olan yerleşik bir sınıftır, bu yüzden hızlı bir şekilde kodlayabildiğim herhangi bir MutableInteger'den daha kolay ve muhtemelen daha optimize edilmiştir. Bununla birlikte, sınıfın kötüye kullanılması gibi geliyor.


7

Java 8'de atom sınıfları iki ilginç fonksiyonla genişletildi:

  • int getAndUpdate (IntUnaryOperator updateFunction)
  • int updateAndGet (IntUnaryOperator updateFunction)

Her ikisi de atomik değerin güncellenmesi için updateFunction kullanıyor. Fark, birincisinin eski değeri, ikincisinin yeni değeri döndürmesidir. UpdateFunction, standart olandan daha karmaşık "karşılaştırma ve ayarlama" işlemleri yapmak için uygulanabilir. Örneğin, atom sayacının sıfırın altına düşmediğini kontrol edebilir, normalde senkronizasyon gerektirir ve burada kod kilitsizdir:

    public class Counter {

      private final AtomicInteger number;

      public Counter(int number) {
        this.number = new AtomicInteger(number);
      }

      /** @return true if still can decrease */
      public boolean dec() {
        // updateAndGet(fn) executed atomically:
        return number.updateAndGet(n -> (n > 0) ? n - 1 : n) > 0;
      }
    }

Kod, Java Atomic Örneği'nden alınmıştır .


5

Birden çok iş parçacığından erişilebilen veya oluşturulabilen nesnelere Ids vermek istediğimde genellikle AtomicInteger kullanıyorum ve genellikle nesnelerin yapıcısında eriştiğim sınıfta statik bir öznitelik olarak kullanıyorum.


4

Atomik tamsayılarda veya uzun ürünlerde CompareAndSwap (CAS) kullanarak engellemesiz kilitler uygulayabilirsiniz. "TL2" Yazılım İşlemsel Bellek kağıdı bu açıklar:

İşlem yapılan her bellek konumu ile özel bir sürüm yazma kilidi ilişkilendiriyoruz. En basit haliyle, sürüm yazma kilidi, kilidi elde etmek için bir CAS işlemi ve onu serbest bırakmak için bir mağaza kullanan tek bir sözcük döndürme kilididir. Kilidin alındığını belirtmek için yalnızca tek bir bit gerektiğinden, kilit kelimesinin geri kalanını sürüm numarasını tutmak için kullanırız.

Açıkladığı şey, önce atom tamsayısını okumaktır. Bunu yok sayılan bir kilit bitine ve sürüm numarasına bölün. CAS'ı, geçerli sürüm numarasıyla kilit biti kümesine ve sonraki sürüm numarasına temizlenmiş olan kilit biti olarak yazmayı deneyin. Başarılı oluncaya kadar döngü yapın ve kilidin sahibi olan iş parçacığınız olun. Geçerli sürüm numarasını kilit biti temizlenmiş olarak ayarlayarak kilidi açın. Bu makale, iş parçacıklarının yazarken tutarlı bir okuma kümesine sahip olmasını koordine etmek için kilitlerdeki sürüm numaralarının kullanılmasını açıklar.

Bu makalede , işlemcilerin karşılaştırma ve takas işlemleri için donanım desteğine sahip oldukları açıklanmaktadır. Ayrıca iddia ediyor:

Atomik değişkenler kullanan bloke olmayan CAS tabanlı sayaçlar, düşük ila orta düzeyde çekişmede kilit tabanlı sayaçlardan daha iyi performansa sahiptir


3

Anahtar, eşzamanlı erişim ve modifikasyonun güvenli bir şekilde yapılmasına izin vermesidir. Genellikle çok iş parçacıklı bir ortamda sayaç olarak kullanılırlar - girişlerinden önce bu, senkronize bloklarda çeşitli yöntemleri tamamlayan kullanıcı tarafından yazılmış bir sınıf olmalıdır.


Anlıyorum. Bir özniteliğin veya örneğin bir uygulama içinde bir tür küresel değişken gibi davrandığı durumlarda bu olur. Ya da aklınıza gelebilecek başka durumlar var mı?
James P.

1

AtomicInteger'ı Yemek Filozofunun problemini çözmek için kullandım.

Benim çözümümde, çatalları temsil etmek için AtomicInteger örnekleri kullanıldı, filozof başına iki tane gerekli. Her Filozof, 1 ila 5 arasında bir tamsayı olarak tanımlanır. Bir filozof tarafından çatal kullanıldığında, AtomicInteger, 1 ila 5 arasındaki filozofun değerini tutar, aksi takdirde çatal kullanılmaz, bu nedenle AtomicInteger değeri -1 olur .

AtomicInteger daha sonra bir çatalın serbest olup olmadığını kontrol etmenizi sağlar, == - 1 değerini ve bir atomik işlemde çatalın sahibine ayarlar. Aşağıdaki koda bakınız.

AtomicInteger fork0 = neededForks[0];//neededForks is an array that holds the forks needed per Philosopher
AtomicInteger fork1 = neededForks[1];
while(true){    
    if (Hungry) {
        //if fork is free (==-1) then grab it by denoting who took it
        if (!fork0.compareAndSet(-1, p) || !fork1.compareAndSet(-1, p)) {
          //at least one fork was not succesfully grabbed, release both and try again later
            fork0.compareAndSet(p, -1);
            fork1.compareAndSet(p, -1);
            try {
                synchronized (lock) {//sleep and get notified later when a philosopher puts down one fork                    
                    lock.wait();//try again later, goes back up the loop
                }
            } catch (InterruptedException e) {}

        } else {
            //sucessfully grabbed both forks
            transition(fork_l_free_and_fork_r_free);
        }
    }
}

ComparAndSet yöntemi engellenmediğinden, verimi artırmalı, daha fazla iş yapmalıdır. Bildiğiniz gibi, Yemek Felsefecileri problemi kaynaklara erişimin kontrol edilmesi gerektiğinde, yani çatallar gerektiğinde, örneğin bir işlemin çalışmaya devam etmek için kaynaklara ihtiyacı olduğu zaman kullanılır.


0

CompareAndSet () işlevi için basit örnek:

import java.util.concurrent.atomic.AtomicInteger; 

public class GFG { 
    public static void main(String args[]) 
    { 

        // Initially value as 0 
        AtomicInteger val = new AtomicInteger(0); 

        // Prints the updated value 
        System.out.println("Previous value: "
                           + val); 

        // Checks if previous value was 0 
        // and then updates it 
        boolean res = val.compareAndSet(0, 6); 

        // Checks if the value was updated. 
        if (res) 
            System.out.println("The value was"
                               + " updated and it is "
                           + val); 
        else
            System.out.println("The value was "
                               + "not updated"); 
      } 
  } 

Yazdırılan: önceki değer: 0 Değer güncellendi ve 6. Başka bir basit örnek:

    import java.util.concurrent.atomic.AtomicInteger; 

public class GFG { 
    public static void main(String args[]) 
    { 

        // Initially value as 0 
        AtomicInteger val 
            = new AtomicInteger(0); 

        // Prints the updated value 
        System.out.println("Previous value: "
                           + val); 

         // Checks if previous value was 0 
        // and then updates it 
        boolean res = val.compareAndSet(10, 6); 

          // Checks if the value was updated. 
          if (res) 
            System.out.println("The value was"
                               + " updated and it is "
                               + val); 
        else
            System.out.println("The value was "
                               + "not updated"); 
    } 
} 

Yazdırılan: Önceki değer: 0 Değer güncellenmedi

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.