5

以下は簡単なJavaプログラムです。 "cnt"と呼ばれるカウンタがインクリメントされ、 "monitor"というリストに追加されます。 "cnt"は複数のスレッドで増分され、値は複数のスレッドによって "監視"に追加されます。同期ブロックを使用したJavaの並行処理で期待される結果が得られない

"go()"メソッドの最後では、cntとmonitor.size()は同じ値を持つ必要がありますが、そうではありません。 monitor.size()は正しい値を持ちます。

コメント付きの同期ブロックの1つのコメントを解除し、現在コメントを外しているコードをコメントアウトしてコードを変更すると、コードは予期した結果を生成します。また、スレッド数(THREAD_COUNT)を1に設定すると、コードは予期した結果を生成します。

これは、複数の実際のコアを持つマシンでのみ再生できます。

public class ThreadTester { 

    private List<Integer> monitor = new ArrayList<Integer>(); 
    private Integer cnt = 0; 
    private static final int NUM_EVENTS = 2313; 
    private final int THREAD_COUNT = 13; 

    public ThreadTester() { 
    } 

    public void go() { 
     Runnable r = new Runnable() { 

      @Override 
      public void run() { 
       for (int ii=0; ii<NUM_EVENTS; ++ii) { 
        synchronized(monitor) { 
         synchronized(cnt) {  // <-- is this synchronized necessary? 
          monitor.add(cnt); 
         } 
//      synchronized(cnt) { 
//       cnt++;  // <-- why does moving the synchronized block to here result in the correct value for cnt? 
//      } 
        } 
        synchronized(cnt) { 
         cnt++;    // <-- why does moving the synchronized block here result in cnt being wrong? 
        } 
       } 
//    synchronized(cnt) { 
//     cnt += NUM_EVENTS; // <-- moving the synchronized block here results in the correct value for cnt, no surprise 
//    } 
      } 

     }; 
     Thread[] threads = new Thread[THREAD_COUNT]; 

     for (int ii=0; ii<THREAD_COUNT; ++ii) { 
      threads[ii] = new Thread(r); 
     } 
     for (int ii=0; ii<THREAD_COUNT; ++ii) { 
      threads[ii].start(); 
     } 
     for (int ii=0; ii<THREAD_COUNT; ++ii) { 
      try { threads[ii].join(); } catch (InterruptedException e) { } 
     } 

     System.out.println("Both values should be: " + NUM_EVENTS*THREAD_COUNT); 
     synchronized (monitor) { 
      System.out.println("monitor.size() " + monitor.size()); 
     } 
     synchronized (cnt) { 
      System.out.println("cnt " + cnt); 
     } 
    } 

    public static void main(String[] args) { 
     ThreadTester t = new ThreadTester(); 
     t.go(); 

     System.out.println("DONE"); 
    }  
} 

答えて

3

もOKですが、様々な可能性を見てみましょうあなたが言及:

1.

for (int ii=0; ii<NUM_EVENTS; ++ii) { 
    synchronized(monitor) { 
    synchronized(cnt) {  // <-- is this synchronized necessary? 
     monitor.add(cnt); 
    } 
    synchronized(cnt) { 
     cnt++;  // <-- why does moving the synchronized block to here result in the correct value for cnt? 
    } 
} 

まず監視対象は、したがって、その上にロックを取得し、スレッド間で共有される(つまり何が同期されるか)は、ブロック内のコードが一度に1つのスレッドによってのみ実行されるようにします。したがって、外側の2つの同期内部は必要ではなく、コードはとにかに保護されています。

for (int ii=0; ii<NUM_EVENTS; ++ii) { 
    synchronized(monitor) { 
    monitor.add(cnt); 
    } 
    synchronized(cnt) { 
    cnt++;    // <-- why does moving the synchronized block here result in cnt being wrong? 
    } 
} 

2. [OK]を、この1は少しトリッキーです。 cntはIntegerオブジェクトであり、JavaではIntegerオブジェクトの変更を許可しません(Integersは不変です)。コードではこれがここで行われていることを示唆しています。しかし、何が起こるかは、cnt ++が値cnt + 1を持つ新しい整数を作成し、cntをオーバーライドするということです。 これは、コードが実際に何をするかです:

synchronized(cnt) { 
    Integer tmp = new Integer(cnt + 1); 
    cnt = tmp; 
} 

問題は、一つのスレッドが新しいCNTオブジェクトを作成する一方、他のすべてのスレッドが古いのロックを取得するために待っている間ということです。スレッドは古いcntを解放し、新しいcntオブジェクトをロックしようとし、別のスレッドが古いcntオブジェクトをロックしている間に取得しようとします。突然2つのスレッドがクリティカルセクションに入り、同じコードを実行し競合状態を引き起こします。これは間違った結果が出てくる場所です。

最初の同期ブロック(モニタ付きのブロック)を削除すると、レースの可能性が増すため、結果はさらに悪化します。

一般的には、これが起こらないように最終変数でのみsynchronizedを使用するようにしてください。

+0

本当に何が起こっているのかは次のとおりです。 整数tmp =新しい整数(cnt + 1); は私が欠けていた部分でした。私は不変性とオートボケがこれにどのように影響するのかを考えていなかった。 – mangotang

関連する問題