web-dev-qa-db-ja.com

Java 5+で揮発性が別のスレッドからの可視性を保証しないのはなぜですか?

による:

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

bshouldaが1の場合、すべてのスレッドで1になります。

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

68
Oleg

更新:

興味のある方は、このバグに対処し、Java 7u6 buildb14で修正しました。バグレポート/修正はこちらで確認できます。

元の回答

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

スレッドが_a == 1_を確認すると、そのスレッドは_b == 1_を確認することが保証されます。

Post Java 5、OPの例では、while(a == 0)が発生すると、bは1であることが保証されます。

編集:

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

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

私はWindows7を使用していますJava 1.6_24(_31で試してみます)

編集2:

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

編集3:

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

bttにロードすると、フィールドがローカルに格納され、if coniditonal(ttではなくその値への参照)で使用されるようです。したがって、_b == 0_がtrueの場合、おそらくttへのローカルストアが0であったことを意味します(この時点で、ローカルttに1を割り当てる競争です)。これは、クライアントが設定された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番目のエントリは出力しなかった実行からのものです。

0に等しいテストを行う前に、作業実行が正しくロードされ、bが割り当てられたようです。

_  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%の可能性があるようです。

34
John Vint

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

揮発性変数の可視性の影響は、揮発性変数自体の値を超えています。スレッド[〜#〜] a [〜#〜]が揮発性変数に書き込み、続いてスレッド[〜#〜] b [〜#〜]が同じものを読み取る場合変数の場合、揮発性変数に書き込む前に[〜#〜] a [〜#〜]に表示されていたall変数の値はに表示されます。 [〜#〜] b [〜#〜]揮発性変数を読み取った後。

12
assylias

この質問については、並行性の関心のメーリングリストにあるディスカッションスレッドを確認することをお勧めします: http://cs.oswego.edu/pipermail/concurrency-interest/2012-May/009440.html

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

2
sjlee