23

を検討してくださいvolatile int sharedVar。私たちは、JLS が次の保証を提供することを知っています。

  1. プログラム順で値のw書き込みに先行する書き込みスレッドのすべてのアクションは、書き込みアクションです。isharedVarhappens-before
  2. 読み取りスレッドによる値の読み取りの成功iによる値の書き込み。w happens-beforeisharedVarr
  3. 読み取りスレッドによるiからの読み取りの成功プログラム順の後続のすべてのアクション。sharedVarr happens-beforer

ただし、読み取りスレッドがいつvalue を監視するかについては、実時間の保証はまだありませんi。値がこのコントラクトに準拠していることを読み取りスレッドに認識させない実装。

私はこれについてしばらく考えましたが、抜け穴は見当たりませんが、抜け穴があるに違いないと思います。私の推論の抜け穴を指摘してください。

4

5 に答える 5

10

答えとその後の議論は、私の最初の推論を統合しただけであることがわかりました。私は今、証明の方法で何かを持っています:

  1. 書き込みスレッドが実行を開始する前に、読み取りスレッドが完全に実行される場合を考えてみます。
  2. この特定の実行によって作成された同期順序に注意してください。
  3. スレッドを実時間でシフトして、並列に実行されるようにしますが、同じ同期順序を維持します。

Javaメモリモデルは実時間を参照しないため、これを妨げるものはありません。これで、2つのスレッドが読み取りスレッドと並行して実行され、書き込みスレッドによって実行されたアクションがないことを確認できます。QED。

例1:1つの書き込み、1つの読み取りスレッド

この発見を最大限に心に訴え、現実のものにするために、次のプログラムを検討してください。

static volatile int sharedVar;

public static void main(String[] args) throws Exception {
  final long startTime = System.currentTimeMillis();
  final long[] aTimes = new long[5], bTimes = new long[5];
  final Thread
    a = new Thread() { public void run() {
      for (int i = 0; i < 5; i++) {
        sharedVar = 1;
        aTimes[i] = System.currentTimeMillis()-startTime;
        briefPause();
      }
    }},
    b = new Thread() { public void run() {
      for (int i = 0; i < 5; i++) {
        bTimes[i] = sharedVar == 0?
            System.currentTimeMillis()-startTime : -1;
        briefPause();
      }
    }};
  a.start(); b.start();
  a.join(); b.join();
  System.out.println("Thread A wrote 1 at: " + Arrays.toString(aTimes));
  System.out.println("Thread B read 0 at: " + Arrays.toString(bTimes));
}
static void briefPause() {
  try { Thread.sleep(3); }
  catch (InterruptedException e) {throw new RuntimeException(e);}
}

JLSに関する限り、これは法的な出力です。

Thread A wrote 1 at: [0, 2, 5, 7, 9]
Thread B read 0 at: [0, 2, 5, 7, 9]

による誤動作のレポートには依存しないことに注意してくださいcurrentTimeMillis。報告された時間は実際のものです。ただし、実装では、書き込みスレッドのすべてのアクションを、読み取りスレッドのすべてのアクションの後にのみ表示することを選択しました。

例2:読み取りと書き込みの両方の2つのスレッド

今@StephenCは主張し、多くの人が彼に同意するでしょう、それは起こります-明示的に言及していなくても、それでも時間の順序を意味します。したがって、これがどの程度であるかを正確に示す2番目のプログラムを紹介します。

public static void main(String[] args) throws Exception {
  final long startTime = System.currentTimeMillis();
  final long[] aTimes = new long[5], bTimes = new long[5];
  final int[] aVals = new int[5], bVals = new int[5];
  final Thread
    a = new Thread() { public void run() {
      for (int i = 0; i < 5; i++) {
        aVals[i] = sharedVar++;
        aTimes[i] = System.currentTimeMillis()-startTime;
        briefPause();
      }
    }},
    b = new Thread() { public void run() {
      for (int i = 0; i < 5; i++) {
        bVals[i] = sharedVar++;
        bTimes[i] = System.currentTimeMillis()-startTime;
        briefPause();
      }
    }};
  a.start(); b.start();
  a.join(); b.join();
  System.out.format("Thread A read %s at %s\n",
      Arrays.toString(aVals), Arrays.toString(aTimes));
  System.out.format("Thread B read %s at %s\n",
      Arrays.toString(bVals), Arrays.toString(bTimes));
}

コードを理解しやすくするために、これは典型的な実際の結果になります。

Thread A read [0, 2, 3, 6, 8] at [1, 4, 8, 11, 14]
Thread B read [1, 2, 4, 5, 7] at [1, 4, 8, 11, 14]

一方、このようなものを見ることは決して期待できませんが、それでもJMMの基準では合法です。

Thread A read [0, 1, 2, 3, 4] at [1, 4, 8, 11, 14]
Thread B read [5, 6, 7, 8, 9] at [1, 4, 8, 11, 14]

JVMは、スレッドBに時間1で何を読み取らせるかを知るために、実際にはスレッドAが時間14で何を書き込むかを予測する必要があります。これの妥当性、さらには実現可能性は非常に疑わしいものです。

これから、JVM実装が取ることができる次の現実的な自由を定義できます。

スレッドによる中断されない一連のリリースアクションの可視性は、スレッドを中断する取得アクションの前まで安全に延期できます。

リリース取得という用語は、 JLS§17.4.4で定義されています。

このルールの当然の結果として、書き込みのみを行い、読み取りを行わないスレッドのアクションは、発生前の関係に違反することなく無期限に延期できます。

不安定な概念を片付ける

volatile修飾子は、実際には2つの異なる概念についてです。

  1. それに対する行動が起こることを尊重するという確固たる保証-注文する前に;
  2. 書き込みのタイムリーな公開に向けたランタイムの最善の努力のソフトな約束。

ポイント2は、JLSによって指定されていないことに注意してください。これは、一般的な予想によって発生するものです。約束を破る実装は、明らかに準拠しています。時間の経過とともに、超並列アーキテクチャに移行するにつれて、その約束は確かに非常に柔軟であることが証明される可能性があります。したがって、将来的には、保証と約束の組み合わせが不十分であることが判明することを期待しています。要件に応じて、一方が他方なし、一方が他方のフレーバーが異なる、または他の任意の数の組み合わせが必要になります。

于 2012-08-01T18:52:09.357 に答える
4

あなたは部分的に正しいです。r私の理解では、スレッドがスレッドに対して先行発生関係を持つ他の操作に関与していない場合に限り、これは合法であるということwです。

そのため、実時間でいつになるかは保証されません。ただし、プログラム内の他の同期ポイントに関しては保証があります。

(これが気になる場合は、より基本的な意味で、JVM が実際にタイムリーにバイトコードを実行するという保証がないことを考慮してください提供することは本質的に不可能であるため、単純に永久に停止した JVM はほぼ確実に合法です。実行時のハード タイミング保証)。

于 2012-08-01T14:50:51.577 に答える
3

私はもう以下のどれも信じていません。それはすべて、17.4.4 の 2 つの言及を除いて定義されていない「後続」の意味に帰着します。ここでは、トートロジー的に「同期順序に従って定義」されています。)

本当に続けなければならないのは、セクション 17.4.3 だけです。

順次整合性は、プログラムの実行における可視性と順序付けに関して行われる非常に強力な保証です。順次一貫性のある実行では、プログラムの順序と一致するすべての個々のアクション (読み取りや書き込みなど) に全体的な順序があり、個々のアクションはそれぞれアトミックであり、すべてのスレッドにすぐに表示されます。(強調追加)

そのようなリアルタイムの保証があると思いますが、JLS 17 章のさまざまなセクションからつなぎ合わせる必要があります。

  1. セクション 17.4.5 によると、「事前発生関係は、データ競合がいつ発生するかを定義します」。明示的に述べられていないようですが、これは、アクション a が別のアクション a' の前に発生する場合、それらにデータ競合がないことを意味すると思います。
  2. 17.4.3 によると、「一連のアクションは、...変数vの各読み取りrが、実行順序で w が r の前に来るように、 vへの書き込みwによって書き込まれた値を参照する場合、順次一貫性があります...プログラムにデータ競合がない場合、プログラムのすべての実行は順次一貫しているように見えます。」

volatile 変数に書き込みv、その後別のスレッドでそれを読み取る場合、読み取りよりも先に書き込みが行われることを意味します。これは、書き込みと読み取りの間にデータ競合がないことを意味します。つまり、それらは連続的に一貫している必要があります。つまり、読み取りrは、書き込みw (または後続の書き込み)によって書き込まれた値を認識しなければなりません。

于 2012-08-17T21:22:52.793 に答える
3

このセクション (17.4.4)を参照してください。仕様を少しひねったため、混乱しています。volatile 変数の読み取り/書き込み仕様は、具体的には特定の値について何も述べていません。

  • 揮発性変数 (§8.3.1.4) v への書き込みは、任意のスレッドによる v の後続のすべての読み取りと同期します (後続は同期順序に従って定義されます)。

アップデート:

@AndrzejDoyleが言及しているように、その時点以降にスレッドが実行の後の時点でスレッドとの同期ポイントを確立しないr限り、スレッドに古い値を読み取らせることができます(仕様に違反するため)。 . はい、そこにはいくらかのゆらぎの余地がありますが、スレッドができることは非常に制限されます (たとえば、System.out への書き込みは、ほとんどのストリーム impl が同期されるため、後の同期ポイントを確立します)。wr

于 2012-08-01T14:44:58.910 に答える
1

volatileJava の は、「A を見れば B も見える」という言葉で表現されていると思います。

より明確に言うと、Java は、スレッドが volatile 変数fooを読み取って値 A を確認した場合、後で同じスレッドで他の変数を読み取ったときに何が表示されるかについて、いくつかの保証があることを約束します。A を に書き込んだのと同じスレッドが( A を に書き込む前に) fooB にも書き込んだ場合、 に少なくとも B が表示されることが保証されます。barfoobar

もちろん、A に会えなければ、B にも会える保証はありません。また、 B が に表示されたbarとしても、 A が に表示されるかどうかはわかりませんfoofooまた、スレッドが A を書き込んでから、別のスレッドが A を見るまでの経過時間fooは保証されません。

于 2014-12-19T21:49:09.447 に答える