2017-02-23 9 views
2

私はvolatile変数を学習しています。私は揮発性が何であるか知っています、私は揮発性変数のサンプルプログラムを作成しましたが、期待どおりに動作しません。Javaのvolatileキーワードが正常に動作しない

なぜ私は、システムがIが同期に使用すると、変数と値は常に2000

でなければなりません「カウント」キャッシュしてはならないので、揮発使用していた2000年、その後いつかあまり来て「カウント」の最終値がありますそれは正常に動作しますが、volatileキーワードの場合はそうではありません。

public class Worker { 

private volatile int count = 0; 
private int limit = 10000; 

public static void main(String[] args) { 
    Worker worker = new Worker(); 
    worker.doWork(); 
} 

public void doWork() { 
    Thread thread1 = new Thread(new Runnable() { 
     public void run() { 
      for (int i = 0; i < limit; i++) { 

        count++; 

      } 
     } 
    }); 
    thread1.start(); 
    Thread thread2 = new Thread(new Runnable() { 
     public void run() { 
      for (int i = 0; i < limit; i++) { 

        count++; 

      } 
     } 
    }); 
    thread2.start(); 

    try { 
     thread1.join(); 
     thread2.join(); 
    } catch (InterruptedException ignored) {} 
    System.out.println("Count is: " + count); 
} 
} 

ありがとうございます!

+1

volatileとsynchronizedは同じではありません...代わりに原子を使用してください.... –

+2

* "私は揮発性が何であるかを知っています" *違反はありませんが、この質問は別の方法で証明されます。 – Tom

+1

"私は揮発性を使用しているため、システムは競合状態(ここで明白な問題)は別として、"カウント "変数をキャッシュしてはいけません。これは完全に間違っています。揮発性は、CPUがメモリ内の値をキャッシュするのを止めるわけではありません。現代のCPUには無意味なので、このようなことをする方法はありません。揮発性は非常に異なるもので、メモリの順序と可視性の保証を理解していないと実際には使用しないでください(理解した後に、より高度な解決策があることがわかります)。 – Voo

答えて

8

count++を実行すると、それは読み込み、増分、書き込みです。 2つのスレッドがそれぞれ読み取りを行い、それぞれがインクリメントを行い、次にそれぞれが書き込みを行い、結果的にインクリメントが1つだけになります。あなたの読み取りはアトミックですが、書き込みはアトミックであり、値はキャッシュされません。それ以上のものが必要です。アトミックのリード・モディファイ・ライト・オペレーションが必要で、volatileはそれを提供しません。

count++
+0

良い説明!! –

2

は、基本的にはこれです:

// 1. read/load count 
// 2. increment count 
// 3. store count 
count = count + 1; 

個別firstthird操作がアトミックです。それらの3つすべてtogetherは原子ではありません。

1

i++ is not atomic in Java。したがって、2つのスレッドが同時に読み取られる場合は、両方とも+1が同じ数であると計算し、両方が同じ結果を格納します。

javac inc.javaを使用して、これをコンパイルします。

public class inc { 
    static int i = 0; 
    public static void main(String[] args) { 
     i++; 
    } 
} 

javap -c incを使用してバイトコードを読みます。 getstaticiconst_1iaddputstaticを:私たちは(静的なint型の)その増加分を使用して実装されている参照

public class inc { 
    static int i; 

    public static void main(java.lang.String[]); 
    Code: 
     0: getstatic  #2     // Field i:I 
     3: iconst_1 
     4: iadd 
     5: putstatic  #2     // Field i:I 
     8: return 
} 

:私はちょうど機能main()を表示するには、これをトリムダウンしました。

これは4つの命令とロックなしで行われるため、アトミック性は期待できません。また、これは1つの命令で行われた場合でも、我々は(this threadでユーザー「ホット・リックス」のコメントからの引用)運の外にあってもよいことは注目に値する:「増分保管場所」命令を実装しても、ハードウェア上の

スレッドセーフであるという保証はありません。ただ1つの演算子で表すことができるという理由だけで、スレッドセーフティについては何も言いません。


あなたが実際にこの問題を解決したい場合は、アトミック保証を持っている、AtomicIntegerを使用することができます。

final AtomicInteger myCoolInt = new AtomicInteger(0); 
myCoolInt.incrementAndGet(1); 
1

予想通り、それがあればことを保証しますので、あなたはそれが働いていた​​方法を使用する場合そのスレッドの1つがそのメソッドを実行し、他の呼び出し元スレッドの実行は、現在実行中のスレッドがメソッドを終了するまで中断されます。この場合、リードインクリメントライトサイクル全体はアトミックです。 tutorialから

:同じオブジェクトの同期メソッド の2つの呼び出しがインターリーブするため

まず、それは不可能です。あるスレッドがオブジェクトの同期メソッド を実行しているとき、 を呼び出す他のすべてのスレッドは、同じオブジェクトブロック(実行を中断)のメソッドを同期させます。最初のスレッドがオブジェクトで完了するまで、 。

第2に、同期メソッドが終了すると、 同期メソッドの後続の呼び出しと同じオブジェクトに対して自動的に happen-beforeリレーションシップが確立されます。これにより、オブジェクトの状態への変更 がすべてのスレッドに表示されることが保証されます。

このサイクルは、getと増分ステップ間、他のスレッド上の変数には、他の書き込みがないことを保証するものではありません、このキーワードを使用するなど、アトミックではありません(他の人が説明するように)あなたがvolatileを使用このスレッドで

​​キーワードではなく、原子数をカウントする場合は、たとえばAtomicInteger:ここ

public class Worker { 
    private AtomicInteger count = new AtomicInteger(0); 
    private int limit = 10000; 

    public static void main(String[] args) { 
     Worker worker = new Worker(); 
     worker.doWork(); 
    } 

    public void doWork() { 
     Thread thread1 = new Thread(new Runnable() { 
      public void run() { 
       for (int i = 0; i < limit; i++) 
        count.getAndIncrement(); 
      } 
     }); 
     thread1.start(); 
     Thread thread2 = new Thread(new Runnable() { 
      public void run() { 
       for (int i = 0; i < limit; i++) 
        count.getAndIncrement(); 
      } 
     }); 

     thread2.start(); 

     try { 
      thread1.join(); 
      thread2.join(); 
     } catch (InterruptedException ignored) { 
     } 
     System.out.println("Count is: " + count); 
    } 
} 

getAndIncrement()アトミック読み取りインクリメント・セットサイクルを保証します。

1

メモリの可視性と原子性は、マルチスレッドでは2つの異なるが一般的な問題です。 synchronizedキーワードを使用すると、ロックを取得して両方を保証します。 volatileはメモリの可視性の問題のみを解決します。 彼の本Concurrency in practiceで、Brain Goetzはいつvolatileを使うべきか説明します。

  1. その現在の値に依存しない変数への書き込み、またはあなた は、単一のスレッドが値を更新していることを確認することができます。
  2. この変数は他の状態の不変式には含まれません 変数;
  3. 変数に がアクセスされている間は、他の理由でロックする必要はありません。

あなたのケースでは、アトミックではない操作数++を見てください。

関連する問題