56

volatileインスタンス変数の読み取り/書き込みが2つあり、ロックを解除するオーバーヘッド(または潜在的なデッドロックのリスク)が必要ない場合は、インスタンス変数を使用することがあります。たとえば、タイマースレッドは、あるクラスでゲッターとして公開されているintIDを定期的に更新します。

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を使用することに意味はありますか?(つまり、64ビット)。

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

4

3 に答える 3

131

私があなたの質問を正しく理解しているかどうかはわかりませんが、JLS8.3.1.4。揮発性フィールドの状態:

フィールドは揮発性として宣言される場合があります。その場合、Javaメモリモデルは、すべてのスレッドが変数の一貫した値を参照することを保証します(§17.4)。

そして、おそらくもっと重要なのは、JLS 17.7ダブルおよびロングの非原子的処理

17.7doubleおよびlongの非アトミック処理
[...]
Javaプログラミング言語のメモリモデルの目的上、不揮発性longまたはdouble値への1回の書き込みは、2つの別々の書き込みとして扱われます。各32ビットに1回です。半分。これにより、スレッドが1つの書き込みから64ビット値の最初の32ビットを認識し、別の書き込みから次の32ビットを認識する状況が発生する可能性があります。揮発性のlong値とdouble値の書き込みと読み取りは、常にアトミックです。参照の書き込みと読み取りは、32ビット値と64ビット値のどちらで実装されているかに関係なく、常にアトミックです。

つまり、「全体」の変数は、2つの部分だけでなく、volatile修飾子によって保護されます。これは、読み取りでさえ不揮発性のlong / doubleの場合はアトミックではないため、 sの場合よりもsの場合にvolatileを使用することがさらに重要であると主張するように誘惑します。longint

于 2010-06-14T14:54:00.903 に答える
15

これは例によって示すことができます

  • 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では、数分で1つのエラーを取得できませんでした。

于 2015-12-22T21:23:38.920 に答える
6

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

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

もっとありますか?

于 2015-12-22T21:34:48.340 に答える