2016-11-26 9 views
3

ここでは、の改良である:[STACK-REF] In Java, is it required to synchronize write access to an array if each thread writes to a separate cell space?Java:配列の内容は、他のスレッドによって更新された後、自動的に更新されます。同期ステートメントはありません。

コンテキスト:我々は、統計的計算を行うために、行型の巨大なアレイに必要。 [STACK-REF]配列コンテンツをN(ワーカー)スレッドで同時に処理できることを発見しましょう。

問題:メインスレッドT0がN個のワーカーを起動します。すべての労働者が終わったら、T0は何をアレイで見ていますか?その主体はメインメモリと同期していますか?

(単純な)テストケース:8件の商品:

  • スレッドT0バイトの小さな配列を作成します。
  • 8つの別個の値を書き込む新しい新しいスレッドT1を開始します。
  • T0は、ウェイクアップ前にT1が終了するように、長時間待ってください。 T1は、それを構築したようT0はそれを見て:
  • アレイ含量は(T0によって)

検索結果を表示します。 これはかなり正常なのか偶発的なのでしょうか?

public class TestCase extends Thread { 

public static void main(String[] args) throws InterruptedException { 

    //Current thread (main) stands for 'T0' in topic description. 

    // byte : to get a very small array. 
    byte[] counts = { (byte) 128, (byte) 129, (byte) 130, 
          (byte) 131, (byte) 132, (byte) 133, 
          (byte) 134, (byte) 135 }; 

    StringBuilder sb = new StringBuilder(); 
    for (int i = 0; i < counts.length; i++) 
     sb.append(counts[i]+" "); 

    // all values are displays as negative numbers 
    System.out.println("'counts' initial content = " + sb); 

    Thread T1 = new TestCase(counts); 
    T1.start(); 


    // 'join' can't be suspected to do refresh 'counts' array. 
    // T1.join(); 
    Thread.sleep(1000); 

    //Avoid to display each item, to avoid any unexpected 
    // sync, as explained in : 
    // see : https://stackoverflow.com/questions/21583448/what-can-force-a-non-volatile-variable-to-be-refreshed 
    //  for (int i = 0; i < counts.length; i++) 
    //   System.out.println(counts[i]+" "); 


    sb = new StringBuilder(); 
    for (int i = 0; i < counts.length; i++) 
     sb.append(counts[i]+" "); 

    // all values are displayed as written by thread T1 
    //How 'counts' copy in L1 cache has been updated from main memory ? 
    System.out.println("'counts' post content = " + sb); 

} 

配列のサイズが小さいと、我々は、各スレッドがそのL1/2/...キャッシュ(> = 32KO)での完全なコピーを所有していることを想像してみてください。 T1が終了すると、アレイはメインメモリで完全に更新されます。

質問:T0ウェイクアップ時に、特別な指示なしにアレイの '更新された'ビューを取得する方法はありますか?

私たちは[STACK-REF]で述べられているような条件下で、巨大なint配列と多くのスレッドを含む、より複雑なテストを構築しました。毎回、スレッドT0(すなわち、「メイン」のスレッド)は、アレイの完全な一貫したビューを得た。

はい、私はその

Thread Caching and Java Memory model

主張知っている: 「各スレッドがメモリのローカルコピーを持っていません...」 が、答えは明らかに私たちの問題を説明していません。

@Basilevs

私はThread.join()が同期を行うことを期待しています。なぜあなたはそれを拒否しますか?

テストケースの初期バージョンを書くときは、Thread.join()ステートメントを使用しました。私は同じ結果を得た。

あなたのように、私は同期のためのjoin()リクエストを想像しました。私はこの仮説をテストするために、sleep()でjoin()を置き換えました。

...I'd recommend not to sleep while waiting ..., thread rescheduling is likely to synchronize thread's context of wake-up. 

私はあなたと完全に同意します。 sleep()ステートメントがループに置き換えられました:

while(System.currentTimeMillis() - t0 < 2000);私はあなたの提案に基づいて新しいテストを投稿しました。

もう一度、矛盾は見られませんでした。

@Andrey Cheboksarov

(おっと、私は自分のPCを記述するために省略:!Win7の64-ビット、jdk8、procのインテルXeonプロセッサE5630)

...あなたはこの配列にいくつかの計算を行う場合その後、ほとんど常に更新された値

私はで活動中T1を保ち、それをテストが表示されますメインスレッドを、スレッド、バックグラウンドで停止o-opループ。 : あなたが正しいです。

... This will print something like -11 100 100 100 ... 

右。

質問1:あなたの答えは他のプロセッサでも有効ですか?例:Power PC?

質問2:私のテストカスの結果がかなり正常であることを確認してください。

+0

私は 'Thread.join()'が同期を実行することを期待しています。なぜあなたはそれを拒否しますか? – Basilevs

+0

結果を待っている間にスリープ状態にならないように矛盾を観察するには、スレッドの再スケジューリングがスレッドのスリープ解除のコンテキストを同期させる可能性があります。 – Basilevs

+0

可視性の保証は、保証された非可視性と同じではありません。キャッシュが無効にされている理由は多々ありますが、それらを第2に推測するのは、ほとんど無意味なものです。 – biziclop

答えて

1

これは良い質問です。私はあなたが必要とする答えから始めます。 cache coherenceというものがあります。これがT1の値を見る主な理由です。基本的には、書き込みまたは読み出しのために特定のコアによってキャッシュラインを取得することができ、1つのコアだけが書き込みのためのキャッシュラインを要求できます。別のコアがそのキャッシュラインを読み取ろうとする場合、このコアラインのすべてのコアとメモリコントローラに要求します。メモリコントローラは、このキャッシュラインが書き込みのために要求されたので、それは静かなままであり、書き込みのためのキャッシュラインを所有するコアは、そのキャッシュラインを要求中のコアに送信すべきであることを知っている。

キャッシュはスマートで、多くの場合、データレースが表示されないようにするためにすべてのことを行うことを理解しています。 Very good and short paper about that coherency and memory barriers。あなたは、この配列でいくつかの計算を行い、バックグラウンドスレッドで停止した場合、メインスレッドはほとんど常に更新された値を参照すると言うことができる例については。データ競合の影響を観察する最も簡単な方法は、ループ内に変数を書き込むことです。良いコンパイラは変数をレジスタに置き、このレジスタだけをインクリメントします。ここでは、コンパイラはまた何かを行っている必要があります。これは よう-11 100 100 100 100 100 100 100 100 100 100 100 100 100 100 100 100 100

注何かを印刷します

byte[] counts = { (byte) 100, (byte) 129, (byte) 130, 
      (byte) 131, (byte) 132, (byte) 133, 
      (byte) 134, (byte) 135 }; 

    ForkJoinPool.commonPool().submit(
      () -> { 
       for(int i = 0; i < 100_000_000; i++) { 
        counts[0]++; 
       } 
      } 
    ); 

    ForkJoinPool.commonPool().submit(
      () -> { 
       for(int i = 0; i < 10_000_000; i++) { 
        System.out.println(counts[0]); 
        try { 
         Thread.sleep(100L); 
        } catch (InterruptedException e) { 
         e.printStackTrace(); 
        } 
       } 
      } 
    ); 

    Thread.sleep(50000L); 

簡単な例を考えてみましょう。どのコードが実際にアセンブリを見ているか見るには

関連する問題