Ne zaman kullanıyoruz AtomicReference
?
Tüm çok iş parçacıklı programlarda nesne oluşturulması gerekiyor mu?
AtomicReference'ın kullanılması gereken basit bir örnek verin.
Ne zaman kullanıyoruz AtomicReference
?
Tüm çok iş parçacıklı programlarda nesne oluşturulması gerekiyor mu?
AtomicReference'ın kullanılması gereken basit bir örnek verin.
Yanıtlar:
Atomik referans , bir referansta monitör tabanlı senkronizasyonun uygun olmadığı basit atomik (yani, iş parçacığı açısından güvenli , önemsiz olmayan) işlemler yapmanız gereken bir ortamda kullanılmalıdır . Belirli bir alanın yalnızca nesnenin durumu en son kontrol ettiğiniz gibi kalıp kalmadığını kontrol etmek istediğinizi varsayalım:
AtomicReference<Object> cache = new AtomicReference<Object>();
Object cachedValue = new Object();
cache.set(cachedValue);
//... time passes ...
Object cachedValueToUpdate = cache.get();
//... do some work to transform cachedValueToUpdate into a new version
Object newValue = someFunctionOfOld(cachedValueToUpdate);
boolean success = cache.compareAndSet(cachedValue,cachedValueToUpdate);
Atomik referans anlambilimi nedeniyle, cache
nesne kullanılmadan iş parçacıkları arasında paylaşılsa bile bunu yapabilirsiniz synchronized
. Genel olarak, ne yaptığınızı bilmiyorsanız java.util.concurrent
, çıplak yerine senkronizörleri veya çerçeveyi kullanmaktan daha iyidir Atomic*
.
Sizi bu konuya tanıtacak iki mükemmel ölü ağaç referansı:
(Bu her zaman doğru olup olmadığını bilmiyorum) referans atamanın (yani =
) atomik olduğunu (atomik gibi ilkel 64 bit türlerini güncelleme long
veya double
atomik olmayabilir; ancak bir referansın güncellenmesi her zaman atomiktir, 64 bit olsa bile) ) açıkça kullanmadan Atomic*
.
Bkz. Java Dil Spesifikasyonu 3ed, Bölüm 17.7 .
AtomicReference
değişkeni işaretlemelisiniz volatile
çünkü çalışma zamanı referans atamasının atomik olduğunu garanti ederken , derleyici değişkenin diğer iş parçacıkları tarafından değiştirilmediğini varsayarak optimizasyonlar yapabilir.
AtomicReference
"; Eğer varsa vardır bunu kullanarak, o zaman benim tavsiyem ters yöne gidip işaretlemek olacaktır final
derleyici buna göre optimize edebilmesi için.
Bir atomik referans, değişmez bir nesnenin durumunu birden fazla iş parçacığı arasında paylaşmanız ve değiştirmeniz gerektiğinde kullanmak için idealdir. Bu süper yoğun bir ifadedir, bu yüzden onu biraz parçalayacağım.
Birincisi, değişmez bir nesne, inşaattan sonra etkili bir şekilde değiştirilmeyen bir nesnedir. Sıklıkla değişmez bir nesnenin yöntemleri aynı sınıfın yeni örneklerini döndürür. Bazı örneklere Long ve Double sarmalayıcı sınıfları ve sadece birkaçını ifade etmek için String dahildir. ( JVM değişmez nesneleri üzerindeki Programlama Eşzamanlılığına göre modern eşzamanlılığın kritik bir parçasıdır).
Daha sonra, AtomicReference, bu paylaşılan değeri paylaşmak için değişken bir nesneden neden daha iyi. Basit bir kod örneği farkı gösterecektir.
volatile String sharedValue;
static final Object lock=new Object();
void modifyString(){
synchronized(lock){
sharedValue=sharedValue+"something to add";
}
}
Bu değişken alan tarafından başvurulan dizeyi geçerli değerine göre her değiştirmek istediğinizde, önce o nesne üzerinde bir kilit almanız gerekir. Bu arada, başka bir iş parçacığının gelmesini ve yeni dize birleştirmesinin ortasında değeri değiştirmesini önler. Sonra iş parçacığınız devam ettiğinde, diğer iş parçacığının çalışmasını hızlandırırsınız. Ama dürüst olmak gerekirse, bu kod işe yarayacak, temiz görünüyor ve çoğu insanı mutlu edecekti.
Hafif sorun. Bu yavaş. Özellikle o kilit Nesne çok çekişme varsa. Çoğu kilitler bir OS sistem çağrısı gerektirdiğinden ve iş parçacığınız diğer işlemlere yol açmak için iş parçacığınızı bloke eder ve CPU'dan bağlam dışı bırakılır.
Diğer seçenek bir AtomicRefrence kullanmaktır.
public static AtomicReference<String> shared = new AtomicReference<>();
String init="Inital Value";
shared.set(init);
//now we will modify that value
boolean success=false;
while(!success){
String prevValue=shared.get();
// do all the work you need to
String newValue=shared.get()+"lets add something";
// Compare and set
success=shared.compareAndSet(prevValue,newValue);
}
Şimdi bu neden daha iyi? Dürüst olmak gerekirse bu kod öncekinden biraz daha az temiz. Ancak AtomicRefrence'da kaputun altında gerçekleşen gerçekten önemli bir şey var ve bu karşılaştırma ve takas. Anahtarın gerçekleşmesini sağlayan bir OS çağrısı değil, tek bir CPU talimatıdır. Bu CPU ile ilgili tek bir talimattır. Ve kilit olmadığından, kilidin kullanıldığı durumda daha fazla zaman kazandıran bağlam anahtarı yoktur!
Yakalama, AtomicReferences için bu bir .equals () çağrısı değil, beklenen değer için bir == karşılaştırması kullanır. Bu nedenle, beklenen öğenin döngüden döndürülen gerçek nesne olduğundan emin olun.
worked
Aynı semantiği elde etmek için döngü yapmalısınız .
AtomicReference için bir kullanım örneği:
Sayı aralığı olarak hareket eden ve alt ve üst sayı sınırlarını korumak için ayrı AtmomicInteger değişkenlerini kullanan bu sınıfı düşünün.
public class NumberRange {
// INVARIANT: lower <= upper
private final AtomicInteger lower = new AtomicInteger(0);
private final AtomicInteger upper = new AtomicInteger(0);
public void setLower(int i) {
// Warning -- unsafe check-then-act
if (i > upper.get())
throw new IllegalArgumentException(
"can't set lower to " + i + " > upper");
lower.set(i);
}
public void setUpper(int i) {
// Warning -- unsafe check-then-act
if (i < lower.get())
throw new IllegalArgumentException(
"can't set upper to " + i + " < lower");
upper.set(i);
}
public boolean isInRange(int i) {
return (i >= lower.get() && i <= upper.get());
}
}
Hem setLower hem de setUpper, kontrol etme ve sonradan hareket etme dizileridir, ancak bunları atom haline getirmek için yeterli kilitleme kullanmazlar. Sayı aralığı (0, 10) tutarsa ve bir iş parçacığı setLower (5) öğesini çağırırken, başka bir iş parçacığı setUpper (4) öğesini çağırırsa, bazı şanssız zamanlamalarla her ikisi de ayarlayıcılardaki denetimleri geçirir ve her iki değişiklik uygulanır. Sonuç olarak, aralık şimdi (5, 4) geçersiz bir durumu tutuyor. Temel AtomicIntegers iş parçacığı için güvenli olsa da, bileşik sınıf değildir. Bu, üst ve alt sınırlar için ayrı AtomicInteger'lar kullanmak yerine bir AtomicReference kullanılarak düzeltilebilir.
public class CasNumberRange {
// Immutable
private static class IntPair {
final int lower; // Invariant: lower <= upper
final int upper;
private IntPair(int lower, int upper) {
this.lower = lower;
this.upper = upper;
}
}
private final AtomicReference<IntPair> values =
new AtomicReference<IntPair>(new IntPair(0, 0));
public int getLower() {
return values.get().lower;
}
public void setLower(int lower) {
while (true) {
IntPair oldv = values.get();
if (lower > oldv.upper)
throw new IllegalArgumentException(
"Can't set lower to " + lower + " > upper");
IntPair newv = new IntPair(lower, oldv.upper);
if (values.compareAndSet(oldv, newv))
return;
}
}
public int getUpper() {
return values.get().upper;
}
public void setUpper(int upper) {
while (true) {
IntPair oldv = values.get();
if (upper < oldv.lower)
throw new IllegalArgumentException(
"Can't set upper to " + upper + " < lower");
IntPair newv = new IntPair(oldv.lower, upper);
if (values.compareAndSet(oldv, newv))
return;
}
}
}
İyimser kilitler uygularken AtomicReference'ı kullanabilirsiniz. Paylaşılan bir nesneniz var ve nesneyi 1'den fazla iş parçacığından değiştirmek istiyorsunuz.
Diğer iş parçacıkları bunu değiştirmiş olabilir ve / veya bu 2 adım arasında değişiklik yapabilir. Atomik bir işlemle yapmanız gerekir. AtomicReference bu noktada yardımcı olabilir
İşte çok basit bir kullanım durumu ve iplik güvenliği ile ilgisi yok.
Bir nesneyi lambda çağrıları arasında paylaşmak için AtomicReference
bir seçenektir :
public void doSomethingUsingLambdas() {
AtomicReference<YourObject> yourObjectRef = new AtomicReference<>();
soSomethingThatTakesALambda(() -> {
yourObjectRef.set(youObject);
});
soSomethingElseThatTakesALambda(() -> {
YourObject yourObject = yourObjectRef.get();
});
}
Bunun iyi bir tasarım ya da herhangi bir şey olduğunu söylemiyorum (sadece önemsiz bir örnek), ancak lambda çağrıları arasında bir nesneyi paylaşmanız gereken bir durum varsa, bu AtomicReference
bir seçenektir.
Aslında, referansı olan herhangi bir nesneyi, hatta yalnızca bir öğeye sahip bir Koleksiyon'u kullanabilirsiniz. Ancak, AtomicReference mükemmel bir uyum sağlar.
Çok konuşmayacağım. Zaten saygı duyduğum arkadaşlarım değerli girdilerini verdiler. Bu blogun sonundaki tam teşekküllü çalışan kod karışıklığı gidermelidir. Bu, çok iş parçacıklı senaryoda bir film koltuğu rezervasyonu küçük programı hakkında.
Bazı önemli temel gerçekler aşağıdaki gibidir. 1> Farklı iş parçacıkları yalnızca yığın alanındaki örneğin ve statik üye değişkenleri için mücadele edebilir. 2> Uçucu okuma veya yazma tamamen atomik ve serileştirilmiş / önce ve sadece bellekten yapılır. Bunu söyleyerek, herhangi bir okumanın bellekteki bir önceki yazıyı takip edeceğini kastediyorum. Ve herhangi bir yazma bellekte önceki okumayı takip edecektir. Bu nedenle, bir uçucu ile çalışan herhangi bir iş parçacığı her zaman en güncel değeri görür. AtomicReference bu uçucu özelliği kullanır.
AtomicReference kaynak kodlarından bazıları aşağıdadır. AtomicReference bir nesne başvurusunu ifade eder. Bu başvuru, AtomicReference örneğinde aşağıdaki gibi geçici bir üye değişkendir.
private volatile V value;
get () sadece değişkenin en son değerini döndürür (uçucular "önce" olur).
public final V get()
AtomicReference'ın en önemli yöntemi aşağıdadır.
public final boolean compareAndSet(V expect, V update) {
return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}
CompareAndSet (wait, update) yöntemi, güvenli olmayan Java sınıfının CompareAndSwapObject () yöntemini çağırır. Güvenli olmayan bu yöntem çağrısı, işlemciye tek bir komut çağıran yerel çağrıyı çağırır. "bekle" ve "güncelleme" her biri bir nesneyi referans alır
Yalnızca ve eğer AtomicReference örneği üye değişkeni "value" aynı nesneye başvuruyorsa, "wait" ile ifade edilirse, şimdi bu örnek değişkenine "update" atanır ve "true" döndürülür. Yoksa yanlış döndürülür. Her şey atomik olarak yapılır. Aralarında başka bir iş parçacığı araya giremez. Bu tek işlemcili bir işlem olduğundan (modern bilgisayar mimarisinin büyüsü), genellikle senkronize bir blok kullanmaktan daha hızlıdır. Ancak , birden fazla değişkenin atomik olarak güncellenmesi gerektiğinde, AtomicReference'ın yardımcı olmayacağını unutmayın.
Tutulmadan çalıştırılabilen tam teşekküllü bir koşu kodu eklemek istiyorum. Birçok karışıklığı giderirdi. Burada 22 kullanıcı (MyTh thread) 20 koltuk rezervasyonu yapmaya çalışıyor. Aşağıda kod snippet'i ve ardından tam kod yer almaktadır.
Kod snippet'i; burada 22 kullanıcı 20 koltuk ayırmaya çalışıyor.
for (int i = 0; i < 20; i++) {// 20 seats
seats.add(new AtomicReference<Integer>());
}
Thread[] ths = new Thread[22];// 22 users
for (int i = 0; i < ths.length; i++) {
ths[i] = new MyTh(seats, i);
ths[i].start();
}
Tam çalışan kod aşağıdadır.
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
public class Solution {
static List<AtomicReference<Integer>> seats;// Movie seats numbered as per
// list index
public static void main(String[] args) throws InterruptedException {
// TODO Auto-generated method stub
seats = new ArrayList<>();
for (int i = 0; i < 20; i++) {// 20 seats
seats.add(new AtomicReference<Integer>());
}
Thread[] ths = new Thread[22];// 22 users
for (int i = 0; i < ths.length; i++) {
ths[i] = new MyTh(seats, i);
ths[i].start();
}
for (Thread t : ths) {
t.join();
}
for (AtomicReference<Integer> seat : seats) {
System.out.print(" " + seat.get());
}
}
/**
* id is the id of the user
*
* @author sankbane
*
*/
static class MyTh extends Thread {// each thread is a user
static AtomicInteger full = new AtomicInteger(0);
List<AtomicReference<Integer>> l;//seats
int id;//id of the users
int seats;
public MyTh(List<AtomicReference<Integer>> list, int userId) {
l = list;
this.id = userId;
seats = list.size();
}
@Override
public void run() {
boolean reserved = false;
try {
while (!reserved && full.get() < seats) {
Thread.sleep(50);
int r = ThreadLocalRandom.current().nextInt(0, seats);// excludes
// seats
//
AtomicReference<Integer> el = l.get(r);
reserved = el.compareAndSet(null, id);// null means no user
// has reserved this
// seat
if (reserved)
full.getAndIncrement();
}
if (!reserved && full.get() == seats)
System.out.println("user " + id + " did not get a seat");
} catch (InterruptedException ie) {
// log it
}
}
}
}
AtomicReference'ı ne zaman kullanıyoruz?
AtomicReference , değişken değerini senkronizasyon kullanmadan atomik olarak güncellemenin esnek bir yoludur.
AtomicReference
tek değişkenler üzerinde kilitsiz iş parçacığı güvenli programlamayı destekler.
Üst düzey eşzamanlı API ile İş Parçacığı güvenliğini sağlamanın birçok yolu vardır . Atomik değişkenler çoklu seçeneklerden biridir.
Lock
nesneler, birçok eşzamanlı uygulamayı basitleştiren kilitleme deyimlerini destekler.
Executors
iş parçacıklarını başlatmak ve yönetmek için üst düzey bir API tanımlayın. Java.util.concurrent tarafından sağlanan yönetici uygulamaları, büyük ölçekli uygulamalar için uygun iş parçacığı havuzu yönetimi sağlar.
Eşzamanlı koleksiyonlar büyük veri koleksiyonlarını yönetmeyi kolaylaştırır ve senkronizasyon ihtiyacını büyük ölçüde azaltabilir.
Atomik değişkenler senkronizasyonu en aza indiren ve bellek tutarlılığı hatalarını önlemeye yardımcı olan özelliklere sahiptir.
AtomicReference'ın kullanılması gereken basit bir örnek verin.
Örnek kod AtomicReference
:
String initialReference = "value 1";
AtomicReference<String> someRef =
new AtomicReference<String>(initialReference);
String newReference = "value 2";
boolean exchanged = someRef.compareAndSet(initialReference, newReference);
System.out.println("exchanged: " + exchanged);
Tüm çok iş parçacıklı programlarda nesne oluşturulması gerekiyor mu?
AtomicReference
Tüm çok iş parçacıklı programlarda kullanmak zorunda değilsiniz .
Tek bir değişkeni korumak istiyorsanız kullanın AtomicReference
. Bir kod bloğunu korumak istiyorsanız, Lock
/ synchronized
etc gibi diğer yapıları kullanın .
Başka bir basit örnek, bir oturum nesnesinde güvenli bir iş parçacığı değişikliği yapmaktır.
public PlayerScore getHighScore() {
ServletContext ctx = getServletConfig().getServletContext();
AtomicReference<PlayerScore> holder
= (AtomicReference<PlayerScore>) ctx.getAttribute("highScore");
return holder.get();
}
public void updateHighScore(PlayerScore newScore) {
ServletContext ctx = getServletConfig().getServletContext();
AtomicReference<PlayerScore> holder
= (AtomicReference<PlayerScore>) ctx.getAttribute("highScore");
while (true) {
HighScore old = holder.get();
if (old.score >= newScore.score)
break;
else if (holder.compareAndSet(old, newScore))
break;
}
}
Kaynak: http://www.ibm.com/developerworks/library/j-jtp09238/index.html