14

プログラム順序ルールには、「スレッド内の各アクションが発生する-プログラム順序の後半にあるそのスレッド内のすべてのアクションの前に」と記載されています。

1.別のスレッドアクション

  • 変数の読み取りと書き込み
  • モニターのロックとロック解除
  • スレッドの開始と結合

これは、読み取りと書き込みを順番に変更できるが、2行目または3行目で指定されたアクションでは読み取りと書き込みの順序を変更できないことを意味しますか?

2.「プログラムオーダー」とはどういう意味ですか?

例を挙げた説明は本当に役に立ちます。

追加の関連質問

次のコードがあるとします。

long tick = System.nanoTime(); //Line1: Note the time
//Block1: some code whose time I wish to measure goes here
long tock = System.nanoTime(); //Line2: Note the time

まず、物事をシンプルに保つためのシングルスレッドアプリケーションです。コンパイラは、時間を2回チェックする必要があることと、周囲の時間注記行との依存関係がないコードのブロックに気付くため、コードを再編成する可能性があり、その結果、Block1がタイミング呼び出しに囲まれなくなる可能性があります。実際の実行中(たとえば、Line1-> Line2-> Block1の順序を検討してください)。しかし、私はプログラマーとして、Line1、2とBlock1の間の依存関係を見ることができます。Line1はBlock1の直前にある必要があり、Block1は完了するまでに有限の時間がかかり、Line2がすぐに続きます。

だから私の質問は:私はブロックを正しく測定していますか?

  • はいの場合、コンパイラが順序を並べ替えることを妨げているのは何ですか。
  • いいえの場合(遠野の答えを読んだ後は正しいと思います)、それを防ぐために何ができますか。

PS:最近SOで尋ねた別の質問からこのコードを盗みました。

4

5 に答える 5

21

そもそもなぜそのようなルールが存在するのかを説明するのに役立つでしょう。

Javaは手続き型言語です。つまり、Javaに何かをする方法を教えます。Javaがあなたが書いた順序であなたの命令を実行しない場合、それは明らかに機能しません。たとえば、以下の例では、Javaが2-> 1-> 3を実行すると、シチューは台無しになります。

1. Take lid off
2. Pour salt in
3. Cook for 3 hours

では、なぜルールは単に「Javaはあなたが書いたものをあなたが書いた順序で実行する」と言っていないのですか?一言で言えば、Javaは賢いからです。次の例を見てください。

1. Take eggs out of the freezer
2. Take lid off
3. Take milk out of the freezer
4. Pour egg and milk in
5. Cook for 3 hours

Javaが私のようだったら、順番に実行するだけです。ただし、Javaは、より効率的であり、1-> 3-> 2-> 4-> 5を実行しても最終結果が同じになることを理解するのに十分賢いです(再び冷凍庫に行く必要はありません。それはレシピを変更しません)。

したがって、「スレッド内の各アクションが発生する-プログラムの順序の後半にあるそのスレッド内のすべてのアクションの前に」というルールが言おうとしているのは、「単一のスレッドでは、プログラムは正確に実行されたのように実行されます。作成した順序。舞台裏で順序を変更する場合がありますが、出力が変更されないようにします。

ここまでは順調ですね。複数のスレッドで同じことをしないのはなぜですか?マルチスレッドプログラミングでは、Javaはそれを自動的に行うのに十分賢いわけではありません。一部の操作(スレッドの結合、スレッドの開始、ロック(モニター)が使用されている場合など)では機能しますが、その他の操作では、プログラム出力を変更するような並べ替えを行わないように明示的に指示する必要があります(volatileフィールドのマーカーなど)。ロックの使用など)。

注:
「関係の前に起こる」に関する簡単な補遺。これは、Javaの並べ替えが何をする場合でも、AのものがBのものの前に発生するという素晴らしい言い方です。後の奇妙なシチューの例では、「ステップ1と3が発生します-ステップ4の前に「卵とミルクを注ぎます」」。また、たとえば、「ステップ1と3は相互に依存しないため、発生前の関係は必要ありません」

コメントへの追加の質問と回答について

まず、プログラミングの世界で「時間」が何を意味するかを確立しましょう。プログラミングでは、「絶対時間」(今の世界の時間は?)と「相対時間」(xからどれくらいの時間が経過したか)という概念があります。理想的な世界では、時は時ですが、原子時計が組み込まれていない限り、絶対時間は時々修正する必要があります。一方、相対的な時間については、イベント間の違いにのみ関心があるため、修正は必要ありません。

JavaではSystem.currentTime()、絶対時間をSystem.nanoTime()扱い、相対時間を扱います。これが、nanoTimeのJavadocが「この方法は経過時間を測定するためにのみ使用でき、システムや壁掛け時計の他の概念とは関係がない」と述べている理由です。

実際には、currentTimeMillisとnanoTimeはどちらもネイティブ呼び出しであるため、コンパイラーは、並べ替えが正確さに影響しないかどうかを実際に証明できません。つまり、実行が並べ替えられません。

しかし、実際にネイティブコードを調べて、合法である限りすべてを並べ替えるコンパイラ実装を作成したいとします。JLSを見ると、「検出できない限り、何でも並べ替えることができます」ということがわかります。コンパイラーの作成者として、並べ替えがセマンティクスに違反するかどうかを判断する必要があります。相対時間(nanoTime)の場合、実行を並べ替えると、明らかに役に立たなくなります(つまり、セマンティクスに違反します)。さて、絶対時間(currentTimeMillis)で並べ替えると、セマンティクスに違反しますか?世界の時刻のソース(たとえばシステム時計)と私たちが決定したもの(「50ms」など)*との差を制限できる限り、私はノーと言います。以下の例の場合:

long tick = System.currentTimeMillis();
result = compute();
long tock = System.currentTimeMillis();
print(result + ":" + tick - tock);

compute()コンパイラが、許可できるシステムクロックからの最大発散よりも少ないことを証明できる場合は、次のようにこれを並べ替えることが合法です。

long tick = System.currentTimeMillis();
long tock = System.currentTimeMillis();
result = compute();
print(result + ":" + tick - tock);

そうすることで、定義した仕様に違反することはなく、したがってセマンティクスに違反することもありません。

また、なぜこれがJLSに含まれていないのかという質問もありました。答えは「JLSを短くする」だと思います。しかし、私はこの領域についてあまり知らないので、別の質問をすることをお勧めします。

*:実際の実装では、この違いはプラットフォームによって異なります。

于 2013-03-27T08:50:19.470 に答える
7

プログラムの順序付け規則は、個々のスレッド内で、コンパイラーによって導入された並べ替えの最適化によって、プログラムが連続して実行された場合に発生した結果とは異なる結果が生成されないことを保証します。同期せずにスレッドの状態が監視された場合に、スレッドのアクションが他のスレッドで発生するように見える順序については保証されません。

このルールは、プログラムの最終的な結果のみを対象としており、そのプログラム内での個々の実行の順序については説明していないことに注意してください。たとえば、いくつかのローカル変数に次の変更を加えるメソッドがある場合:

x = 1;
z = z + 1;
y = 1;

コンパイラーはこれらの操作を自由に並べ替えることができますが、パフォーマンスを向上させるのに最適であると考えています。これを考える1つの方法は、ソースコードでこれらの操作を並べ替えても同じ結果が得られる場合、コンパイラは自由に同じことを実行できるということです。(実際には、さらに進んで、空のメソッドの呼び出しなど、結果がないことが示されている操作を完全に破棄することができます。)

2番目の箇条書きで、モニターロックルールが機能します。「モニターのロック解除が発生します。その後、メインモニターがロックされるたびにロックが解除されます。」(Javaの並行性の実践p。341)これは、特定のロックを取得するスレッドが、そのロックを解放する前に他のスレッドで発生したアクションを一貫して表示することを意味します。ただし、この保証は、2つの異なるスレッドreleaseまたは同じロックacquireの場合にのみ適用されることに注意してください。スレッドAがロックXを解放する前に多くのことを実行し、次にスレッドBがロックYを取得した場合、スレッドBはAのプレXアクションの一貫したビューを持つことが保証されません。

変数への読み取りと書き込みを並べ替えることができます。また、 a。) スレッド内のプログラムの順序が崩れない場合、startおよびb。)変数に他の「発生前」のスレッド同期セマンティクスが適用されていない場合それらは、フィールドに格納することによって言います。joinvolatile

簡単な例:

class ThreadStarter {
   Object a = null;
   Object b = null;
   Thread thread;

   ThreadStarter(Thread threadToStart) {
      this.thread = threadToStart;
   }

   public void aMethod() {
      a = new BeforeStartObject();
      b = new BeforeStartObject();
      thread.start();
      a = new AfterStartObject();
      b = new AfterStartObject();

      a.doSomeStuff();
      b.doSomeStuff();
   }
}

フィールドabメソッドaMethod()はまったく同期されてthreadおらず、開始のアクションはフィールドへの書き込みの結果(またはそれらのフィールドでの処理)を変更しないため、コンパイラーは自由にthread.start()どこにでも並べ替えることができます。メソッド。の順序で実行できなかった唯一のことは、フィールドに書き込みた後にフィールドのaMethod()1つを書き込む順序を移動するか、フィールドに書き込まれる前にフィールドの呼び出しの1つを移動することです。(つまり、そのような並べ替えによって、呼び出しの結果が何らかの形で変わると想定します。)BeforeStartObjectAfterStartObjectdoSomeStuff()AfterStartObjectdoSomeStuff()

ここで覚えておくべき重要なことは、同期がない場合、で開始されたスレッドは、aMethod()理論的にはフィールドのいずれかまたは両方、および実行中にそれらがとる状態(を含む)のいずれかを監視aできるbことaMethod()ですnull

追加の質問回答

割り当てとtickコードの並べ替えは、実際に測定で使用する場合、たとえば、それらの差を計算して結果を出力として出力する場合はtock、並べ替えることができません。このような並べ替えは、Javaのスレッド内のas-if-serialセマンティクスBlock1を明らかに破壊します。指定されたプログラム順序で命令を実行することによって得られる結果から結果を変更します。割り当て測定に使用されず、プログラムの結果にいかなる種類の副作用もない場合、それらは並べ替えられるのではなく、コンパイラーによってノーオペレーションとして最適化される可能性があります。

于 2013-03-27T09:04:39.070 に答える
1

質問に答える前に、

変数の読み取りと書き込み

する必要があります

(同じフィールドの)揮発性読み取りおよび揮発性書き込み

プログラムの順序は、これが関係の前に発生することを保証するものではなく、発生前の関係がプログラムの順序を保証するものです。

あなたの質問に:

これは、読み取りと書き込みを順番に変更できるが、2行目または3行目で指定されたアクションでは読み取りと書き込みの順序を変更できないことを意味しますか?

答えは、実際にはどのアクションが最初に発生し、どのアクションが次に発生するかによって異なります。コンパイラライター向けのJSR133クックブックをご覧ください。発生する可能性のある許可されたコンパイラの並べ替えを一覧表示するCanReorderグリッドがあります。

たとえば、揮発性ストアは通常のストアの上または下で再注文できますが、揮発性ストアは 揮発性負荷の上または下で再注文することはできません。これはすべて、スレッド内のセマンティクスがまだ保持されていることを前提としています。

「プログラムの順序」とはどういう意味ですか?

これはJLSからです

各スレッドtによって実行されるすべてのスレッド間アクションの中で、tのプログラム順序は、tのスレッド内セマンティクスに従ってこれらのアクションが実行される順序を反映する全順序です。

つまり、変数の書き込みとロードを、書き込みとまったく同じ方法で実行されるように変更できる場合は、プログラムの順序が維持されます。

例えば

public static Object getInstance(){
    if(instance == null){
         instance = new Object();
    }
    return instance;
}

に再注文できます

public static Object getInstance(){
     Object temp = instance;
     if(instance == null){
         temp = instance = new Object();
     }
     return temp;
}
于 2013-03-27T12:16:21.797 に答える
0

これは単に、スレッドが多重化されている可能性があることを意味しますが、スレッドのアクション/操作/命令の内部順序は(比較的)一定のままです。

thread1:T1op1、T1op2、T1op3 ... thread2:T2op1、T2op2、T2op3 .. ..

スレッド間の操作の順序(Tn'op'M)は異なる場合がありますがT1op1, T1op2, T1op3、スレッド内の操作は常にこの順序になります。T2op1, T2op2, T2op3

例:

T2op1, T1op1, T1op2, T2op2, T2op3, T1op3
于 2013-03-27T08:32:34.953 に答える
0

Javaチュートリアルhttp://docs.oracle.com/javase/tutorial/essential/concurrency/memconsist.htmlによると、発生する前の関係は、ある特定のステートメントによるメモリ書き込みが別の特定のステートメントに表示されることを保証するだけです。これがイラストです

int x;

synchronized void x() {
    x += 1;
}

synchronized void y() {
    System.out.println(x);
}

synchronized発生前の関係を作成します。これを削除すると、スレッドAの増分xスレッドBが1を出力した後、0を出力する可能性があるという保証はありません。

于 2013-03-27T08:47:06.773 に答える