AtomicIntegerの実用的な使用法


229

私は、AtomicIntegerと他のAtomic変数が同時アクセスを許可することを理解しています。このクラスは通常どのような場合に使用されますか?

回答:


190

主な用途は2つありますAtomicInteger

  • incrementAndGet()多くのスレッドで同時に使用できるアトミックカウンター(など)として

  • 非ブロッキングアルゴリズムを実装するための比較およびスワップ命令(compareAndSet())をサポートするプリミティブとして。

    以下は、BrianGöetzのJava Concurrency In Practiceのノンブロッキング乱数ジェネレーターの例です。

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

    ご覧のとおり、基本的にはとほぼ同じように機能しますが、インクリメントではなくincrementAndGet()任意の計算(calculateNext())を実行します(そして、結果を処理してから戻ります)。


1
初めての使い方は理解できたと思います。これは、属性が再度アクセスされる前に、カウンターがインクリメントされていることを確認するためです。正しい?2番目の使用法の短い例を挙げていただけますか?
James P.

8
最初の使用についてのあなたの理解は一種の真実です-それは単に、別のスレッドがreadwrite that value + 1操作の間のカウンターを変更する場合、古い更新を上書きするのではなく、これを検出することを保証します(「失われた更新」問題を回避します)。これは実際には特殊なケースですcompareAndSet-古い値がだった2場合、クラスは実際に呼び出しますcompareAndSet(2, 3)-そのため、別のスレッドがその間に値を変更した場合、インクリメントメソッドは事実上最初から再開します。
Andrzej Doyle

3
「残り> 0?残り:残り+ n;」この式では、nが0のときに余りをnに追加する理由はありますか?
sandeepkunkunuru、2016

100

私が考えることができる絶対的に単純な例は、アトミック操作をインクリメントすることです。

標準の整数で:

private volatile int counter;

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

AtomicIntegerの場合:

private AtomicInteger counter;

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

後者は、すべてのアクセスの同期に頼る必要なく、単純な変更効果(特にカウント、または一意のインデックス付け)を実行する非常に簡単な方法です。

より複雑な同期フリーロジックを使用して使用することができるcompareAndSet()楽観的ロックのタイプとして-これに基づいて、現在の値、計算結果を取得し、この結果を設定する場合に限っ値がまだ他に、計算を行う再起動するために用いられる入力である-しかし、カウントの例は非常に便利です。AtomicIntegers複数のスレッドが関与しているというヒントがある場合は、カウントおよびVM全体の一意のジェネレーターを使用することがよくあります。これは、操作が非常に簡単であるため、プレーンを使用するのは時期尚早な最適化だとほとんど考えています。ints

ほとんどの場合ints、適切なsynchronized宣言を使用して同じ同期の保証を達成できますがAtomicInteger、すべてのメソッドで発生する可能性のあるインターリーブやモニターの保持について心配する必要がなく、スレッドの安全性が実際のオブジェクト自体に組み込まれているのが優れています。たまたまint値にアクセスします。正しいモニターのセットを事前に取得するためgetAndIncrement()に戻っi++て覚えている(または覚えていない)ときよりも、呼び出し時に誤ってスレッドセーフ性に違反することははるかに困難です。


2
この明確な説明をありがとう。メソッドがすべて同期されるクラスよりもAtomicIntegerを使用する利点は何ですか?後者は「より重い」と見なされますか?
James P.

3
私の観点からは、それは主にAtomicIntegersで得られるカプセル化です-同期は必要なものに対して正確に行われ、意図した結果が何であるかを説明するためにパブリックAPIにある説明的なメソッドを取得します。(それに加えて、ある程度はあなたが正しいと思いますが、クラスのすべてのメソッドを単純に同期してしまい、粒度が粗すぎる可能性がありますが、HotSpotがロックの最適化と時期尚早の最適化に対するルールを実行しているため、読みやすさはパフォーマンスよりも大きなメリット。)
Andrzej Doyle

これは非常に明確で正確な説明です、ありがとう!!
Akash5288 2017

最後に、私のためにそれを適切に片付けた説明。
ベニーボッテマ

58

AtomicIntegerのメソッドを見ると、intでの一般的な操作に対応する傾向があることがわかります。例えば:

static AtomicInteger i;

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

これのスレッドセーフバージョンです:

static int i;

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

方法は次のようにマッピングします
++ii.incrementAndGet()
i++i.getAndIncrement()
--iているi.decrementAndGet()
i--i.getAndDecrement()
i = xi.set(x)
x = iていますx = i.get()

compareAndSetまたはのような他の便利な方法もありますaddAndGet


37

の主な用途はAtomicInteger、マルチスレッドコンテキストで、を使用せずに整数に対してスレッドセーフな操作を実行する必要がある場合ですsynchronized。プリミティブ型の割り当てと取得intはすでにアトミックですが、アトミックAtomicIntegerではない多くの操作が付属していintます。

最も簡単なものはありますgetAndXXXxXXAndGet。たとえばgetAndIncrement()i++これはアトミックではありませんが、実際には3つの操作(取得、追加、割り当て)のショートカットであるため、アトミックではありません。compareAndSetセマフォ、ロック、ラッチなどの実装に非常に役立ちます。

を使用するAtomicIntegerと、同期を使用して同じことを実行するよりも高速で読みやすくなります。

簡単なテスト:

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を搭載した私のPCでは、アトミックテストは3秒で実行され、同期テストは約5.5秒で実行されます。ここでの問題は、同期する操作(notAtomic++)が本当に短いことです。したがって、同期のコストは操作と比較して非常に重要です。

原子性のほかに、AtomicIntegerはInteger、たとえばMap値としてsの可変バージョンとして使用できます。


1
AtomicIntegerデフォルトのequals()実装を使用しているため、マップキーとして使用したくないと思います。これは、マップで使用した場合のセマンティクスが期待するものとはほぼ異なります。
Andrzej Doyle

1
@Andrzej確かに、変更不可である必要があるキーではなく値です。
gabuzo、2011年

@gabuzo原子整数が同期よりもうまく機能する理由はありますか?
Supun Wijerathne 2017年

テストはかなり古い(6年以上)ので、最近のJREで再テストするのは興味深いかもしれません。私はAtomicIntegerを深く理解していませんでしたが、これは非常に特殊なタスクであるため、この特定のケースでのみ機能する同期技術を使用します。また、テストはモノスレッド化されており、負荷の高い環境で同様のテストを行っても、AtomicIntegerにそのような明確な勝利が得られない可能性があることに注意してください
gabuzo

私はその3ミリ秒と5.5ミリ秒を信じています
Sathesh 2018

17

たとえば、あるクラスのインスタンスを生成するライブラリがあります。これらのインスタンスはサーバーに送信されるコマンドを表し、各コマンドには一意のIDが必要であるため、これらの各インスタンスには一意の整数IDが必要です。複数のスレッドが同時にコマンドを送信できるため、AtomicIntegerを使用してそれらのIDを生成しています。別のアプローチは、ある種のロックと通常の整数を使用することですが、それはどちらも遅く、エレガントではありません。


この実用的な例を共有していただきありがとうございます。これは、プログラムにインポートするファイルごとに一意のIDが必要なので、使用する必要があるように思えます:)
James P.

7

gabuzoが言ったように、参照でintを渡したいときにAtomicIntegersを使用することがあります。これは、アーキテクチャ固有のコードを持つ組み込みクラスであるため、すばやくコード化できるMutableIntegerよりも簡単で、最適化されている可能性があります。そうは言っても、それはクラスの虐待のように感じます。


7

Java 8では、アトミッククラスが2つの興味深い関数で拡張されています。

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

どちらもupdateFunctionを使用して、アトミック値の更新を実行しています。違いは、1つ目は古い値を返し、2つ目は新しい値を返すことです。updateFunctionを実装すると、標準の操作よりも複雑な「比較および設定」操作を実行できます。たとえば、アトミックカウンターがゼロ未満にならないことを確認できます。通常は同期が必要ですが、ここではコードがロックされていません。

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

コードはJava Atomic Exampleから取得されました。


5

複数のスレッドからアクセスまたは作成できるオブジェクトにIDを与える必要がある場合は、通常AtomicIntegerを使用します。通常、オブジェクトのコンストラクターでアクセスするクラスの静的属性として使用します。


4

アトミック整数またはlongに対してcompareAndSwap(CAS)を使用して、非ブロッキングロックを実装できます。「TL2」ソフトウェアトランザクショナルメモリ紙はこれを説明します。

トランザクション処理されたすべてのメモリロケーションに、特別なバージョンの書き込みロックを関連付けます。最も単純な形式では、バージョン付きの書き込みロックは、CAS操作を使用してロックを取得し、ストアを使用してロックを解放する単一ワードのスピンロックです。ロックが取得されたことを示すために必要なのは1ビットだけなので、残りのロックワードを使用してバージョン番号を保持します。

それが説明していることは、最初に原子整数を読むことです。これを、無視されたロックビットとバージョン番号に分割します。現在のバージョン番号でロックビットがクリアされた状態でロックビットセットと次のバージョン番号にCAS書き込みを試みます。成功するまでループし、自分がロックを所有するスレッドになります。ロックビットをクリアした状態で現在のバージョン番号を設定してロックを解除します。このペーパーでは、ロックのバージョン番号を使用して、スレッドが書き込み時に一貫した読み取りセットを持つように調整する方法について説明しています。

この記事では、プロセッサがハードウェアで比較およびスワップ操作をサポートしているため、非常に効率的であると説明しています。それはまた主張します:

アトミック変数を使用する非ブロッキングCASベースのカウンターは、低から中程度の競合でロックベースのカウンターよりも優れたパフォーマンスを発揮します


3

重要なのは、同時アクセスと変更を安全に許可することです。これらは、マルチスレッド環境でカウンターとして一般的に使用されます。導入前は、さまざまなメソッドを同期ブロックにラップするユーザー作成クラスである必要がありました。


そうですか。これは、属性またはインスタンスがアプリケーション内で一種のグローバル変数として機能する場合に発生します。それともあなたが考えることができる他のケースはありますか?
James P.

1

私はAtomicIntegerを使ってダイニング哲学者の問題を解決しました。

私のソリューションでは、AtomicIntegerインスタンスを使用してフォークを表現しました。哲学者ごとに2つ必要です。各哲学者は1〜5の整数として識別されます。哲学者がフォークを使用すると、AtomicIntegerは哲学者の値を1〜5に保持します。それ以外の場合、フォークは使用されないため、AtomicIntegerの値は-1になります。 。

次に、AtomicIntegerを使用すると、1つのアトミック操作で、フォークが空いている(値==-1)かどうかを確認し、空いている場合はフォークの所有者に設定できます。以下のコードを参照してください。

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

compareAndSetメソッドはブロックしないため、スループットが向上し、より多くの作業が行われます。ご存じかもしれませんが、Dining Philosophers問題は、プロセスが作業を継続するためにリソースを必要とするように、リソースへの制御されたアクセスが必要な場合、つまりフォークが必要な場合に使用されます。


0

compareAndSet()関数の簡単な例:

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

印刷されるのは:以前の値:0値は更新され、6です別の簡単な例:

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

印刷されたもの:以前の値:0値は更新されませんでした

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