JavaでAtomicReferenceを使用する場合


311

いつ使用しAtomicReferenceますか?

すべてのマルチスレッドプログラムでオブジェクトを作成する必要がありますか?

AtomicReferenceを使用する必要がある簡単な例を示します。

回答:


215

アトミック参照は、モニターベースの同期が適切でない、参照に対して単純なアトミック(つまり、スレッドセーフ、非自明)操作を実行する必要がある設定で使用する必要があります。オブジェクトの状態が最後に確認したとおりに残っている場合にのみ、特定のフィールドかどうかを確認するとします。

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

アトミック参照セマンティクスにより、cacheオブジェクトをスレッド間で共有している場合でも、を使用せずにこれを行うことができますsynchronized。一般的には、何をしているのかを理解していない限り、シンクロナイザーやjava.util.concurrentフレームワークを使用する方が無難Atomic*です。

このトピックを紹介する2つの優れたデッドツリー参照:

(これは常に真となっている場合、私は知りません)なお、参照(すなわち割り当て=自体はアトミックである)(更新プリミティブのような64ビットの種類をlongdoubleアトミックではないかもしれない。しかし、更新、参照することは、それは64ビットだ場合でも、常にアトミックです)を明示的に使用せずにAtomic*Java言語仕様3ed、セクション17.7を
参照してください。


43
私が間違っている場合は訂正してください。しかし、これが必要になるのは、「compareAndSet」を実行する必要があるためです。私がする必要があるすべてが設定された場合、参照の更新自体がアトミックであるため、AtomicObjectはまったく必要ありませんか?
sMoZely 2013年

cache.compareAndSet(cachedValue、someFunctionOfOld(cachedValueToUpdate))を実行しても安全ですか?つまり、計算をインライン化しますか?
kaqqao 2013

4
@veggen Javaの関数引数は関数自体の前に評価されるため、この場合はインライン化によって違いはありません。はい、安全です。
ドミトリー

29
@sMoZelyそれは正しいですが、あなたが使用していない場合AtomicReferenceは、変数をマークする必要がありvolatileながらので、ランタイムはその参照の割り当てを保証アトミックで、コンパイラは変数が他のスレッドによって変更されていなかったという仮定の下での最適化を行うことができます。
kbolino 2014年

1
@BradCupit AtomicReference「使用していない場合」と言ったことに注意してください。あなたそれを使用しているなら、私のアドバイスは反対の方向に行きfinal、コンパイラーがそれに応じて最適化できるようにそれをマークすることです。
kbolino、2015

91

アトミック参照は、複数のスレッド間で不変オブジェクトの状態を共有および変更する必要がある場合に使用するのが理想的です。これは非常に高密度のステートメントなので、少し詳しく説明します。

まず、不変オブジェクトは、構築後に実質的に変更されないオブジェクトです。不変オブジェクトのメソッドは、同じクラスの新しいインスタンスを返すことがよくあります。いくつかの例としては、LongとDoubleのラッパークラス、およびStringが含まれます。(JVMでの並行プログラミングによると不変オブジェクトは最新の並行性の重要な部分です)。

次に、その共有値を共有するために、AtomicReferenceが揮発性オブジェクトよりも優れている理由。簡単なコード例で違いを示します。

volatile String sharedValue;
static final Object lock=new Object();
void modifyString(){
  synchronized(lock){
    sharedValue=sharedValue+"something to add";
  }
}

現在の値に基づいて揮発性フィールドによって参照される文字列を変更するたびに、まずそのオブジェクトのロックを取得する必要があります。これにより、その間に他のスレッドが入り、新しい文字列連結の途中で値が変更されるのを防ぎます。その後、スレッドが再開すると、他のスレッドの作業を妨害します。しかし、正直に言って、そのコードは機能し、見た目はきれいで、ほとんどの人を幸せにします。

わずかな問題。遅いです。特に、そのロックオブジェクトの競合が多い場合。ほとんどのロックにはOSシステムコールが必要であり、スレッドはブロックされ、CPUからコンテキストが切り替えられて他のプロセスに代わるからです。

他のオプションは、AtomicRefrenceを使用することです。

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

なぜこれが良いのでしょうか?正直なところ、そのコードは以前より少しクリーンではありません。しかし、AtomicRefrenceの内部で起こる重要なことがあり、それは比較と交換です。切り替えを発生させるのは、OS呼び出しではなく、単一のCPU命令です。これは、CPU上の単一の命令です。また、ロックがないため、ロックが実行された場合のコンテキストスイッチがなく、さらに時間を節約できます。

キャッチは、AtomicReferencesの場合、これは.equals()呼び出しを使用せず、代わりに期待値の==比較を使用します。したがって、ループ内のgetから返された実際のオブジェクトが期待されるものであることを確認してください。


14
2つの例の動作は異なります。worked同じセマンティクスを取得するには、ループする必要があります。
CurtainDog 2014年

5
AtomicReferenceコンストラクター内で値を初期化する必要があると思います。そうしないと、shared.setを呼び出す前に、別のスレッドで値nullが表示される可能性があります。(shared.setが静的イニシャライザで実行されている場合を除きます)
Henno Vermeulen

8
2番目の例では、Java 8以降で次のようなものを使用する必要があります。shared.updateAndGet((x)->(x + "lets add something")); ...機能するまで.compareAndSetを繰り返し呼び出します。これは、常に成功する同期ブロックと同等です。渡されるラムダは複数回呼び出される可能性があるため、副作用がないことを確認する必要があります。
トム・ディブル

2
揮発性の文字列をsharedValueにする必要はありません。synchronized(lock)は、関係の前に発生を確立するのに十分です。
Jai Pandit 2017

2
"...不変オブジェクトの状態を変更する"はここでは不正確です。リテラルであるため、不変オブジェクトの状態を変更できないためです。この例は、ある不変オブジェクトインスタンスから別のインスタンスへの参照の変更を示しています。私はそれが奇妙なことだと思いますが、スレッドロジックがどれほど混乱する可能性があるかを考えると、強調する価値があると思います。
マークフィリップス

30

AtomicReferenceの使用例を次に示します。

数値範囲として機能し、個々のAtmomicInteger変数を使用して、数値の下限と上限を維持するこのクラスを考えます。

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

setLowerとsetUpperはどちらもcheck-then-actシーケンスですが、アトミックにするために十分なロックを使用していません。数値の範囲が(0、10)で、1つのスレッドがsetLower(5)を呼び出し、別のスレッドがsetUpper(4)を呼び出す場合、不運なタイミングで、両方がセッターのチェックに合格し、両方の変更が適用されます。その結果、範囲は無効な状態(5、4)を保持するようになります。したがって、基になるAtomicIntegerはスレッドセーフですが、複合クラスはそうではありません。これは、上限と下限に個別のAtomicIntegerを使用する代わりに、AtomicReferenceを使用することで修正できます。

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
この記事はあなたの答えに似ていますが、より複雑なことについて深く掘り下げています。それは面白い!ibm.com/developerworks/java/library/j-jtp04186
LppEdd

20

楽観的ロックを適用する場合、AtomicReferenceを使用できます。共有オブジェクトがあり、それを複数のスレッドから変更したい。

  1. 共有オブジェクトのコピーを作成できます
  2. 共有オブジェクトを変更する
  3. 共有オブジェクトが以前と同じであることを確認する必要があります。そうである場合は、変更されたコピーの参照で更新します。

他のスレッドがそれを変更したか、これらの2つのステップの間で変更できる可能性があるため。アトミック操作でそれを行う必要があります。これはAtomicReferenceが役立つ場所です


7

これは非常に単純な使用例であり、スレッドの安全性とは何の関係もありません。

ラムダ呼び出し間でオブジェクトを共有するにAtomicReferenceは、これがオプションです:

public void doSomethingUsingLambdas() {

    AtomicReference<YourObject> yourObjectRef = new AtomicReference<>();

    soSomethingThatTakesALambda(() -> {
        yourObjectRef.set(youObject);
    });

    soSomethingElseThatTakesALambda(() -> {
        YourObject yourObject = yourObjectRef.get();
    });
}

これが良いデザインだとか言っているわけではありませんが(それはほんの些細な例です)、ラムダ呼び出し間でオブジェクトを共有する必要がある場合は、 AtomicReferenceはオプションです。

実際、参照を保持する任意のオブジェクトを使用でき、アイテムが1つしかないコレクションも使用できます。ただし、AtomicReferenceは最適です。


6

あまり話さない。私の尊敬する仲間の友人はすでに貴重な意見を述べています。このブログの最後にある本格的な実行コードは、混乱を取り除くはずです。それはマルチスレッドのシナリオで映画の座席予約の小さなプログラムについてです。

いくつかの重要な基本的な事実は次のとおりです。1>異なるスレッドは、ヒープスペース内のインスタンスおよび静的メンバー変数のみと競合できます。2>揮発性の読み取りまたは書き込みは完全にアトミックであり、以前はシリアル化され、メモリからのみ行われます。これを言うことで、すべての読み取りがメモリ内の以前の書き込みに続くことを意味します。そして、書き込みはメモリからの前回の読み取りに従います。そのため、volatileを使用するスレッドは常に最新の値を参照します。 AtomicReferenceはこの揮発性のプロパティを使用します。

以下は、AtomicReferenceのソースコードの一部です。AtomicReferenceはオブジェクト参照を参照します。この参照は、以下のようにAtomicReferenceインスタンスの揮発性メンバー変数です。

private volatile V value;

get()は、変数の最新の値を返します(揮発性変数が「前に起こる」方法で行うように)。

public final V get()

以下は、AtomicReferenceの最も重要なメソッドです。

public final boolean  compareAndSet(V expect, V update) {
        return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}

compareAndSet(expect、update)メソッドは、Javaの安全でないクラスのcompareAndSwapObject()メソッドを呼び出します。unsafeのこのメソッド呼び出しは、ネイティブの呼び出しを呼び出します。これは、プロセッサへの単一の命令を呼び出します。「expect」と「update」はそれぞれオブジェクトを参照します。

AtomicReferenceインスタンスメンバー変数「値」が同じオブジェクトを参照する場合に限り、「期待」によって「更新」がこのインスタンス変数に割り当てられ、「true」が返されます。それ以外の場合は、falseが返されます。すべてがアトミックに行われます。他のスレッドがその間に傍受することはできません。これはシングルプロセッサ操作(最新のコンピューターアーキテクチャの魔法)であるため、同期ブロックを使用するよりも高速であることがよくあります。だが、複数の変数をアトミックに更新する必要がある場合、AtomicReferenceは役に立ちません。

Eclipseで実行できる本格的な実行コードを追加したいと思います。それは多くの混乱を取り除くでしょう。ここでは22人のユーザー(MyThスレッド)が20席を予約しようとしています。以下は、完全なコードが後に続くコードスニペットです。

22人のユーザーが20席を予約しようとしているコードスニペット。

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

以下は実行中の完全なコードです。

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はいつ使用しますか?

AtomicReferenceは、同期を使用せずに変数値をアトミックに更新する柔軟な方法です。

AtomicReference 単一の変数でロックフリーのスレッドセーフプログラミングをサポートします。

高レベルの並行 APIでスレッドセーフを実現するには、いくつかの方法があります。原子変数は、複数のオプションの1つです。

Lock オブジェクトは、多くの同時アプリケーションを簡略化するロックイディオムをサポートしています。

Executorsスレッドを起動および管理するための高レベルAPIを定義します。java.util.concurrentによって提供されるエグゼキューター実装は、大規模なアプリケーションに適したスレッドプール管理を提供します。

並行コレクションを使用すると、大量のデータのコレクションを管理しやすくなり、同期の必要性を大幅に減らすことができます。

アトミック変数には、同期を最小限に抑え、メモリの整合性エラーを回避するのに役立つ機能があります。

AtomicReferenceを使用する必要がある簡単な例を示します。

のサンプルコード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);

すべてのマルチスレッドプログラムでオブジェクトを作成する必要がありますか?

AtomicReferenceすべてのマルチスレッドプログラムで使用する必要はありません。

単一の変数を保護する場合は、を使用しますAtomicReference。コードブロックを保護する場合は、Lock/ synchronizedなどの他の構成を使用します。


-1

もう1つの簡単な例は、セッションオブジェクトでセーフスレッド変更を行うことです。

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

出典:http : //www.ibm.com/developerworks/library/j-jtp09238/index.html

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.