1. 概要

このチュートリアルでは、AtomicIntegerのようなJava atomicクラスのメソッドset() lazySet()の違いを見ていきます。 およびAtomicReference

2. 原子変数–簡単な要約

Javaのアトミック変数を使用すると、モニターやミューテックスなどの同時実行プリミティブを追加しなくても、クラス参照またはフィールドに対してスレッドセーフな操作を簡単に実行できます。

これらはjava.util.concurrent.atomic パッケージで定義されており、APIはアトミックタイプによって異なりますが、ほとんどが set() lazySet()メソッド。

簡単にするために、この記事ではAtomicReferenceAtomicIntegerを使用しますが、他のアトミックタイプにも同じ原則が適用されます。

3. set()メソッド

set()メソッドは、揮発性フィールドへの書き込みと同等です。

set()、を呼び出した後、別のスレッドから get()メソッドを使用してフィールドにアクセスすると、変更がすぐに表示されます。 これは、値がCPUキャッシュからすべてのCPUコアに共通のメモリレイヤーにフラッシュされたことを意味します。

上記の機能を紹介するために、最小限のproducer-consumerコンソールアプリを作成しましょう。

public class Application {

    AtomicInteger atomic = new AtomicInteger(0);

    public static void main(String[] args) {
        Application app = new Application();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                app.atomic.set(i);
                System.out.println("Set: " + i);
                Thread.sleep(100);
            }
        }).start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                synchronized (app.atomic) {
                    int counter = app.atomic.get();
                    System.out.println("Get: " + counter);
                }
                Thread.sleep(100);
            }
        }).start();
    }
}

コンソールに、一連の「設定」および「取得」メッセージが表示されます。

Set: 3
Set: 4
Get: 4
Get: 5

cache coherence を示すのは、「Get」ステートメントの値が常にその上の「Set」ステートメントの値以上であるという事実です。

この動作は非常に便利ですが、パフォーマンスが低下します。 キャッシュコヒーレンスが必要ない場合にそれを回避できれば素晴らしいと思います。

4. lazySet()メソッド

lazySet()メソッドは set()メソッドと同じですが、キャッシュフラッシュがありません。

つまり、変更は最終的に他のスレッドにのみ表示されますこれは、別のスレッドから更新されたAtomicReferenceでget()を呼び出すと、古い値が得られる可能性があることを意味します。

これが実際に動作することを確認するために、以前のコンソールアプリで最初のスレッドのRunnableを変更してみましょう。

for (int i = 0; i < 10; i++) {
    app.atomic.lazySet(i);
    System.out.println("Set: " + i);
    Thread.sleep(100);
}

新しい「Set」および「Get」メッセージは、常に増分しているとは限りません。

Set: 4
Set: 5
Get: 4
Get: 5

スレッドの性質上、この動作をトリガーするには、アプリを数回再実行する必要がある場合があります。 プロデューサースレッドがAtomicIntegerを5に設定していても、コンシューマースレッドが最初に値4を取得するという事実は、 lazySet()が使用されたときにシステムが結果整合性を持つことを意味します。

より専門的な用語では、lazySet()メソッドは、対応する set()とは対照的に、コード内のエッジの前で発生するようには機能しないと言います。

5. lazySet()を使用する場合

set()との違いは微妙であるため、 lazySet()をいつ使用すべきかはすぐにはわかりません。 問題を注意深く分析して、パフォーマンスを向上させるだけでなく、マルチスレッド環境での正確性を確保する必要があります。

これを使用する1つの方法は、オブジェクト参照が不要になったらnullに置き換えることです。このようにして、パフォーマンスの低下を招くことなく、オブジェクトがガベージコレクションの対象であることを示します。 他のスレッドは、AtomicReferencenullであることがわかるまで、非推奨の値で動作できると想定しています。

ただし、一般的に、アトミック変数に変更を加える場合はlazySet()を使用する必要があり、変更が他のスレッドにすぐに表示される必要はないことがわかっています。

6. 結論

この記事では、アトミッククラスの set()メソッドと lazySet()メソッドの違いについて説明しました。 また、いつどの方法を使用するかについても学びました。

いつものように、例のソースコードはGitHubにあります。