Yanıtlar:
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).
read
ve write that value + 1
iş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ı, 2
sı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.
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 AtomicIntegers
dahil 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 ints
ve uygun synchronized
beyanları 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 int
değ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 .
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:
++i
edilir i.incrementAndGet()
i++
olduğu i.getAndIncrement()
--i
olduğu i.decrementAndGet()
i--
olduğu i.getAndDecrement()
i = x
olduğu i.set(x)
x = i
edilmektedirx = i.get()
Diğer kolaylık yöntemleri yanı vardır gibi compareAndSet
yaaddAndGet
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 int
zaten atomiktir, ancak atomik AtomicInteger
olmayan birçok işlemle birlikte gelir int
.
En basit olanı getAndXXX
veya 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. compareAndSet
semafor, kilit, mandal vb. uygulamalarda çok faydalıdır.
Bunu kullanmak, AtomicInteger
senkronizasyonu 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 Map
değerler olarak değişken bir versiyonu olarak kullanılabilir.
AtomicInteger
Bir 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.
Ö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.
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.
Java 8'de atom sınıfları iki ilginç fonksiyonla genişletildi:
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 .
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.
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
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.
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.
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