Java'da AtomicReference ne zaman kullanılır?


311

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:


215

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, cachenesne 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 longveya doubleatomik 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 .


43
Yanılıyorsam beni düzeltin, ama buna ihtiyaç duymanın anahtarı bir "CompareAndSet" yapmanız gerektiğidir. Tek yapmam gereken ayarlanmış olsaydı, referans güncellemelerinin atomik olması nedeniyle AtomicObject'e hiç ihtiyacım yok mu?
sMoZely

Cache.compareAndSet (cachedValue, someFunctionOfOld (cachedValueToUpdate)) yapmak güvenli mi? Yani hesaplama satır içi?
kaqqao

4
@veggen Java'daki işlev bağımsız değişkenleri işlevin kendisinden önce değerlendirilir, bu nedenle satır içi bu durumda fark etmez. Evet, güvende.
Dmitry

29
@sMoZely Bu doğru, ancak kullanmıyorsanız AtomicReferencedeğ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.
kbolino

1
"Eğer eğer @BradCupit not dedim değil kullanarak AtomicReference"; Eğer varsa vardır bunu kullanarak, o zaman benim tavsiyem ters yöne gidip işaretlemek olacaktır finalderleyici buna göre optimize edebilmesi için.
kbolino

91

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.


14
İki örneğiniz farklı davranıyor. workedAynı semantiği elde etmek için döngü yapmalısınız .
CurtainDog

5
AtomicReference yapıcısı içindeki değeri başlatmanız gerektiğini düşünüyorum, aksi takdirde paylaşımlı.set çağırmadan önce başka bir iş parçacığı hala null değerini görebilirsiniz. (Paylaşılmadığı sürece statik bir başlatıcıda çalıştırılmaz.)
Henno Vermeulen

8
İkinci örneğinizde, Java 8'den itibaren şöyle bir şey kullanmalısınız: shared.updateAndGet ((x) -> (x + "bir şey ekleyelim")); ... çalışana kadar .compareAndSet öğesini tekrar tekrar çağıracaktır. Bu, her zaman başarılı olacak senkronize bloğa eşdeğerdir. Geçtiğiniz lambda'nın yan etkisiz olduğundan emin olmanız gerekir, çünkü birden fazla kez çağrılabilir.
Tom Dibble

2
Uçucu Dize paylaşılanDeğeri yapmanıza gerek yoktur. Senkronize (kilit), ilişkiden önce gerçekleşmeyi sağlayacak kadar iyidir.
Jai Pandit

2
"... değişmez bir cismin durumunu değiştirme" burada kesin değildir, kısmen değişmez bir değişmez cismin durumunu değiştiremezsiniz. Örnek, referansın bir değiştirilemez nesne örneğinden farklı bir örneğe değiştirilmesini gösterir. Bunun bilgiç olduğunun farkındayım, ancak Thread mantığının ne kadar kafa karıştırıcı olabileceği göz önüne alındığında vurgulamaya değer olduğunu düşünüyorum.
Mark Phillips

30

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;
        }
    }
}

2
Bu makale cevabınıza benzer, ancak daha karmaşık şeylere derinlemesine gider. İlginç! ibm.com/developerworks/java/library/j-jtp04186
LppEdd

20

İyimser kilitler uygularken AtomicReference'ı kullanabilirsiniz. Paylaşılan bir nesneniz var ve nesneyi 1'den fazla iş parçacığından değiştirmek istiyorsunuz.

  1. Paylaşılan nesnenin bir kopyasını oluşturabilirsiniz
  2. Paylaşılan nesneyi değiştirme
  3. Paylaşılan nesnenin hala öncekiyle aynı olup olmadığını kontrol etmeniz gerekir - evet ise, değiştirilen kopyanın referansı ile güncelleyin.

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


7

İş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 AtomicReferencebir 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 AtomicReferencebir 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.


6

Ç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
            }
        }
    }

}    

5

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.

Executorsiş 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?

AtomicReferenceTü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/ synchronizedetc gibi diğer yapıları kullanın .


-1

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

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.