web-dev-qa-db-ja.com

volatileを使用することに意味はありますか?

2つのスレッドで読み取り/書き込みを行い、ロックを取得するオーバーヘッド(または潜在的なデッドロックリスク)を望まない場合は、volatileインスタンス変数をときどき使用します。たとえば、いくつかのクラスでゲッターとして公開されるint IDを定期的に更新するタイマースレッド:

public class MyClass {
  private volatile int id;

  public MyClass() {
    ScheduledExecutorService execService = Executors.newScheduledThreadPool(1);
    execService.scheduleAtFixedRate(new Runnable() {
      public void run() {
        ++id;
      }
    }, 0L, 30L, TimeUnit.SECONDS);
  }

  public int getId() {
    return id;
  }
}

私の質問:JLSは32ビットの読み取りがアトミックであることを保証するだけであることを考えると、揮発性のlongを使用してeverにポイントがありますか? (つまり、64ビット)。

警告volatileよりもsynchronizedを使用することが最適化前のケースであると言って返信しないでください。 synchronizedをいつどのように使用するかはよく知っていますが、volatileが望ましい場合もあります。たとえば、Springコンテキストがメインスレッドで各Beanのプロパティを初期化する保証がないため、シングルスレッドアプリケーションで使用するSpring Beanを定義するときは、volatileインスタンス変数を使用する傾向があります。

54
Adamski

これは例で示すことができます

  • 2つのフィールドを常にトグルします。1つは揮発性とマークされ、もう1つはすべてのビットセットとすべてのビットクリアの間ではありません。
  • 別のスレッドでフィールド値を読み取る
  • fooフィールド(volatileで保護されていない)が矛盾した状態で読み取られる可能性があることを確認してください。これは、volatileで保護されたbarフィールドには決して起こりません。

コード

public class VolatileTest {
    private long foo;
    private volatile long bar;
    private static final long A = 0xffffffffffffffffl;
    private static final long B = 0;
    private int clock;
    public VolatileTest() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    foo = clock % 2 == 0 ? A : B;
                    bar = clock % 2 == 0 ? A : B;
                    clock++;
                }
            }

        }).start();
        while (true) {
            long fooRead = foo;
            if (fooRead != A && fooRead != B) {
                System.err.println("foo incomplete write " + Long.toHexString(fooRead));
            }
            long barRead = bar;
            if (barRead != A && barRead != B) {
                System.err.println("bar incomplete write " + Long.toHexString(barRead));
            }
        }
    }

    public static void main(String[] args) {
        new VolatileTest();
    }
}

出力

foo incomplete write ffffffff00000000
foo incomplete write ffffffff00000000
foo incomplete write ffffffff
foo incomplete write ffffffff00000000

これは、32ビットVM、64ビットで実行しているときにのみ発生しますVM数分で単一のエラーを取得できませんでした。

9
Adam

「揮発性」には複数の目的があります。

  • double/longへのアトミックな書き込みを保証します
  • スレッドAがスレッドBによって行われた揮発性変数の変更を確認すると、スレッドAは、揮発性変数の変更前にスレッドBによって行われた他のすべての変更も確認できることを保証します(セル自体を設定した後で、配列内の使用済みセルの数を設定するとします)。 。
  • 1つのスレッドのみが変数を変更できるという仮定に基づいてコンパイラーの最適化を防止します(タイトループwhile (l != 0) {}と考えてください)。

もっとありますか?

4
Arkadiy