2017-10-28 15 views
0

私は今読んでいます。Javaで考えると、同期についての章です。理解できない例があります。Javaでの同期 - Javaで考える例

public abstract class IntGenerator { 

    private volatile boolean canceled = false; 

    public abstract int next(); 

    public void cancel() { 
     canceled = true; 
    } 

    public boolean isCanceled() { 
     return canceled; 
    } 
} 

public class EvenGenerator extends IntGenerator { 

    private int currentEvenValue = 0; 

    final Object object = new Object(); 

    @Override 
    public int next() { 
     ++currentEvenValue; 
     ++currentEvenValue; 
     return currentEvenValue; 
    } 

    public static void main(String[] args) { 
     EvenChecker.test(new EvenGenerator()); 
    } 
} 

public class EvenChecker implements Runnable { 

    private IntGenerator generator; 
    private final int id; 

    public EvenChecker(IntGenerator generator, int id) { 
     this.generator = generator; 
     this.id = id; 
    } 

    @Override 
    public void run() { 
     while (!generator.isCanceled()) { 
      int val = generator.next(); 
      if (val % 2 != 0) { 
       System.out.println(val + " odd"); 
       generator.cancel(); 
      } 
     } 
    } 

    public static void test(IntGenerator generator, int count) { 
     System.out.println("To finish press Ctrl + C"); 
     final ExecutorService executorService = Executors.newCachedThreadPool(); 
     for (int i = 0; i < count; i++) { 
      executorService.execute(new EvenChecker(generator, i)); 
     } 
    } 

    public static void test(IntGenerator generator) { 
     test(generator, 10); 
    } 
} 

そして例の出力は次のようになります。

1239 odd 
1237 odd 
1239 odd 

そして、私はそれを理解しています。これは、最初のインクリメント後に3つのスレッドがcurrentValueを読み込むことを意味します。この問題の

ソリューションは、次のとおりです。間違いのない

public class SynchronizedEvenGenerator extends IntGenerator { 

    private int currentEvenValue = 0; 

    @Override 
    public synchronized int next() { 
     ++currentEvenValue; 
     Thread.yield(); 
     ++currentEvenValue; 
     return currentEvenValue; 
    } 

    public static void main(String[] args) { 
     EvenChecker.test(new SynchronizedEvenGenerator()); 
    } 
} 

今のプログラムが動作している無限大。 私はこの方法でのみ増分を同期しようとした:

public class SynchronizedEvenGenerator extends IntGenerator { 

    private int currentEvenValue = 0; 

    @Override 
    public int next() { 
     synchronized (this) { 
      ++currentEvenValue; 
      Thread.yield(); 
      ++currentEvenValue; 
     } 
     return currentEvenValue; 
    } 

    public static void main(String[] args) { 
     EvenChecker.test(new SynchronizedEvenGenerator()); 
    } 
} 

しかし、今の例アウトプットは次のとおりです。

345 odd 

そして、両方の増分場合currentValueは奇数の値を読み出すことが可能である理由を私は理解できませんどのスレッドも第1と第2のインクリメントの間でcurrentValueを読み取ることができません。

なぜこの出力が得られますか?どのように働く​​?

答えて

2

最終的な例のreturn currentEventValue;ステートメントは​​ブロック内にありません。だから、仮定するスレッドAとスレッドBが両方next()を呼び出します。

スレッドA:

  • 同期さ
  • インクリメントcurrentEventValue
  • インクリメントcurrentEventValue
  • (値が偶数再びです)(値は現在、奇数です)
  • はブロック​​を残します。

スレッドB:

  • インクリメントを同期currentEventValue

スレッドA(値は、現在奇数):

  • 戻りcurrentEventValue(奇数)

スレッドB:currentEventValue(値が偶数再び)

  • インクリメント
  • ​​
  • ブロックを残します。
  • は偶数の値を返します。
1
  • currentEvenValueは342
  • スレッド1である同期ブロックを入力することなく、
  • スレッド1ずつを待つ必要があり、同期ブロック
  • スレッド2回の試行に入る二回currentEvenValue、その値は現在344
  • ですスレッド1が同期ブロックを離れる
  • スレッド2が同期ブロックに入り、最初にcurrentEvenValueをインクリメントするので、値は今すぐ345
  • スレッド1は、currentEvenValueの値を読み取り、それを返し、それを印刷:345

ルールは単純である:すべてが共有状態へのアクセス、読み取りまたは書き込み、同期されなければなりません。