69

によると:

http://www.ibm.com/developerworks/library/j-jtp03304/

新しいメモリ モデルでは、スレッド A が揮発性変数 V に書き込み、スレッド B が V から読み取る場合、V が書き込まれた時点で A に表示されていた変数値は、B に表示されることが保証されます。

また、インターネット上の多くの場所では、次のコードは決して「エラー」を出力してはならないと述べています。

public class Test {
    volatile static private int a;
    static private int b;

    public static void main(String [] args) throws Exception {
        for (int i = 0; i < 100; i++) {
            new Thread() {

                @Override
                public void run() {
                    int tt = b; // makes the jvm cache the value of b

                    while (a==0) {

                    }

                    if (b == 0) {
                        System.out.println("error");
                    }
                }

            }.start();
        }

        b = 1;
        a = 1;
    }
}

b が 1 の場合、すべてのスレッドで 1 にする必要aがあります。

ただし、「エラー」が表示されることがあります。これはどのように可能ですか?

4

4 に答える 4

34

アップデート:

興味のある方は、このバグが対処され、Java 7u6 ビルド b14 で修正されました。ここでバグレポート/修正を確認できます

元の回答

メモリの可視性/順序の観点から考えるときは、その発生前の関係について考える必要があります。の重要な前提条件b != 0は ですa == 1。その場合a != 1、b は 0 または 1 のいずれかになります。

スレッドa == 1が を参照すると、そのスレッドは を参照することが保証されますb == 1

OPの例では、Java 5以降、while(a == 0)ブレークアウトbが1であることが保証されると

編集:

シミュレーションを何度も実行しましたが、出力が表示されませんでした。

どの OS、Java バージョン、および CPU でテストしていますか?

私はWindows 7、Java 1.6_24を使用しています(_31で試しています)

編集2:

OPとWalter Laanへの称賛 - 私にとっては、64ビットのWindows 7で(ただし、除外されない場合があります)、64ビットのJavaから32ビットのJavaに切り替えたときにのみ発生しました。

編集3:

への割り当てtt、またはむしろの staticget はb大きな影響を与えるようです (これを証明するために、 を削除するint tt = b;と、常に機能するはずです。

bintoのロードによりtt、フィールドがローカルに保存され、if coniditonal (その値への参照 not tt) で使用されるようです。したがって、b == 0が true の場合は、おそらくローカル ストア先ttが 0 だったことを意味します (この時点で、 local に 1 を割り当てる競争ttです)。これは、クライアントが設定された 32 ビット Java 1.6 & 7 にのみ当てはまるようです。

2 つの出力アセンブリを比較したところ、すぐに違いがわかりました。(これらはスニペットであることに注意してください)。

これは「エラー」を出力しました

 0x021dd753: test   %eax,0x180100      ;   {poll}
  0x021dd759: cmp    $0x0,%ecx
  0x021dd75c: je     0x021dd748         ;*ifeq
                                        ; - Test$1::run@7 (line 13)
  0x021dd75e: cmp    $0x0,%edx
  0x021dd761: jne    0x021dd788         ;*ifne
                                        ; - Test$1::run@13 (line 17)
  0x021dd767: nop    
  0x021dd768: jmp    0x021dd7b8         ;   {no_reloc}
  0x021dd76d: xchg   %ax,%ax
  0x021dd770: jmp    0x021dd7d2         ; implicit exception: dispatches to 0x021dd7c2
  0x021dd775: nop                       ;*getstatic out
                                        ; - Test$1::run@16 (line 18)
  0x021dd776: cmp    (%ecx),%eax        ; implicit exception: dispatches to 0x021dd7dc
  0x021dd778: mov    $0x39239500,%edx   ;*invokevirtual println

これは「エラー」を出力しませんでした

0x0226d763: test   %eax,0x180100      ;   {poll}
  0x0226d769: cmp    $0x0,%edx
  0x0226d76c: je     0x0226d758         ;*ifeq
                                        ; - Test$1::run@7 (line 13)
  0x0226d76e: mov    $0x341b77f8,%edx   ;   {oop('Test')}
  0x0226d773: mov    0x154(%edx),%edx   ;*getstatic b
                                        ; - Test::access$0@0 (line 3)
                                        ; - Test$1::run@10 (line 17)
  0x0226d779: cmp    $0x0,%edx
  0x0226d77c: jne    0x0226d7a8         ;*ifne
                                        ; - Test$1::run@13 (line 17)
  0x0226d782: nopw   0x0(%eax,%eax,1)
  0x0226d788: jmp    0x0226d7ed         ;   {no_reloc}
  0x0226d78d: xchg   %ax,%ax
  0x0226d790: jmp    0x0226d807         ; implicit exception: dispatches to 0x0226d7f7
  0x0226d795: nop                       ;*getstatic out
                                        ; - Test$1::run@16 (line 18)
  0x0226d796: cmp    (%ecx),%eax        ; implicit exception: dispatches to 0x0226d811
  0x0226d798: mov    $0x39239500,%edx   ;*invokevirtual println

この例では、最初のエントリは「エラー」を出力した実行からのものであり、2 番目はそうでないものからのものです。

b0 に等しいテストを行う前に、作業中の実行が正しく読み込まれ、割り当てられたようです。

  0x0226d76e: mov    $0x341b77f8,%edx   ;   {oop('Test')}
  0x0226d773: mov    0x154(%edx),%edx   ;*getstatic b
                                        ; - Test::access$0@0 (line 3)
                                        ; - Test$1::run@10 (line 17)
  0x0226d779: cmp    $0x0,%edx
  0x0226d77c: jne    0x0226d7a8         ;*ifne
                                        ; - Test$1::run@13 (line 17)

「エラー」を出力した実行では、キャッシュされたバージョンの%edx

  0x021dd75e: cmp    $0x0,%edx
  0x021dd761: jne    0x021dd788         ;*ifne
                                        ; - Test$1::run@13 (line 17)

アセンブラの経験が豊富な方は、参考にしてください :)

編集 4

同時実行開発者が手に入れたので、私の最後の編集になるはず int tt = b;です。割り当ての有無にかかわらず、もう少しテストを行いました。最大値を 100 から 1000 に増やすと、含まれている場合int tt = bは 100% のエラー率があり、除外されている場合は 0% の可能性があるように見えることがわかりました。

于 2012-05-16T15:07:26.080 に答える
12

以下のJCiPからの抜粋に基づいて、あなたの例は決して「エラー」を出力すべきではないと思っていたでしょう:

volatile 変数の可視性効果は、volatile 変数自体の値を超えて拡張されます。スレッドAが揮発性変数に書き込み、続いてスレッドBが同じ変数を読み取ると、揮発性変数に書き込む前にAに表示されていたすべての変数の値が、揮発性変数の読み取り後にBに表示されます。

于 2012-05-16T14:59:04.523 に答える
2

この質問に関する同時実行関心メーリング リストのディスカッション スレッドをチェックしてみてください: http://cs.oswego.edu/pipermail/concurrency-interest/2012-May/009440.html

クライアント JVM (-client) を使用すると、問題をより簡単に再現できるようです。

于 2012-05-17T03:43:32.560 に答える
-2

私の意見では、同期の欠如が原因で問題が発生しました:

注意 : b=1 が a=1 の前に発生し、a が volatile で b がそうでない場合、b=1 は実際には a=1 が終了した後にのみすべてのスレッドに対して更新されます (quate のロジックによる)。

あなたのコードでは、b = 1が最初にメインプロセスに対してのみ更新され、次に揮発性の割り当てが終了したときにのみ、すべてのスレッドbが更新されたことがわかりました。volatile の割り当てはアトミック操作として機能していない可能性があると思います (遠くを指す必要があり、残りの参照を更新して volatile のように振る舞う必要があります)。

私の主張を示すコードへのこの変更を検討してください。

public class Test {
    volatile static private int a;
    static private int b;
    private static Object lock = new Object();


    public static void main(String [] args) throws Exception {
        for (int i = 0; i < 100; i++) {
            new Thread() {

                @Override
                public void run() {
                    int tt = b; // makes the jvm cache the value of b

                    while (true) {
                        synchronized (lock ) {
                            if (a!=0) break;
                         }
                    }

                    if (b == 0) {
                        System.out.println("error");
                    }
                }

            }.start();
        }
        b = 1;
        synchronized (lock ) {
        a = 1;
        }  
    }
}
于 2012-05-16T18:25:57.197 に答える