2016-04-30 13 views
3

複数のスレッドを実行しようとしています。私は明らかに競合状態を取得し、次のようにそれを解決することができています:同期後の競合状態

final Data data = new Data(); 
for (int i = 0; i < numberOfThreads; i++) { 
    final Thread thread = new Thread(new Runnable() { 
     @Override 
     public void run() { 
      //using this sync block to stop the race condition 
      synchronized (data){ 
       final int value = data.getValue(); 
       data.setValue(value + 1); 
      } 
     } 
    }); 
    thread.start(); 
} 

をしかし、私はこのブロックに同期して、代わりにデータのクラスでそれを処理する必要はありません。そこで、私は上記の同期ブロックを削除し、Dataクラスのgetメソッドとsetメソッドを次のように同期させましたが、それでも競合状態が発生します。私はそれらを同期させたのになぜ問題なのですか?個々のメソッドに​​を追加

public class Data { 

    private int value; 

    public synchronized int getValue(){ 
     return this.value; 
    } 

    public synchronized void setValue(int num){ 
     this.value = num; 
    } 
} 
+0

複数のスレッドがgetValue()を同時に呼び出すことができ、getValue()が呼び出された回数だけインクリメントされていなくても、同じ値を書き戻すことができるためです。 – markspace

+0

競合状態がどの程度正確に現れますか? また、スレッドセーフなインクリメントが必要な場合は、[AtomicInteger](https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/AtomicInteger)を使用してください。 html)? –

+0

これは、ダブルチェックロック(https://en.wikipedia.org/wiki/Double-checked_locking)のバリエーションです。答えが示すとおり、取得して別々に設定しています。 – stdunbar

答えて

2

スレッドが非常に明確にGETとセットの間で動けなくなる可能性があり、この

final Data data = new Data(); 
for (int i = 0; i < numberOfThreads; i++) { 
    final Thread thread = new Thread(new Runnable() { 
     @Override 
     public void run() { 
      synchronized (data){ 
       final int value = data.getValue(); 
      } 
      synchronized (data){ 
       data.setValue(value + 1); 
      } 
     } 
    }); 
    thread.start(); 
} 

のようなものをやってに似ています。これを解決するには、タスクを実行するDataクラスに新しい同期メソッドを追加するか、コード内で行ったように​​ブロックに両方の行をラップする必要があります。

4

あなたはしていないので。 2つのスレッドが同時にメソッドを実行することはできませんが、1つのスレッドが実行できるgetValue()getValue()を終了し、setValue()に入る前にもう1つのスレッドが回転し、getValue()が完全に有効で、競合状態です。

Btw。ちょうどDataがクラス全体になる場合は、AtomicIntegerは同じですが、正しく行われます。そこにあなた。 g。あなたの場合の要点である同期ブロックの1つの読み取りと書き込み操作を行うincrementAndGet()メソッドがあります。

1

まず、Dataクラスvaluevolatileである必要があります。

あなたが言及している問題については、コードを変更することはできません。 発生する可能性が次にそれを変更する方法であるので:Thread 1は(valueを読み込みData

1)value0

2である)Thread 0

3)0を読み込む(valueを読み込みます)読み取り0

4)Thread 1増分valueとその手順5であり、ここで)

5(1を書き込み)Data.valueThread 0ずつvalueData.valueに新しい値を書き込む(1を書き込み)

問題に新しい値を書き込む)1ました書かれて以来Thread 0Thread 1が既にThread 0を読んでから増分していることを知らない。

+0

複数のスレッドが同期ブロックの内部でこのフィールドとしか相互作用しない場合、volatileの使用は無関係です。 – KookieMonster

+0

@KookieMonster 'volatile'にはJavaの2つの関数があります。 1)部分的な値が読み書きされないようにするには 2)値をスレッドのローカルキャッシュ/メモリに格納しないようにするには この場合、明らかにケース1を心配する必要はありません技術的には、これを 'int'で心配する必要はありません) しかし、' volatile'を使わない限り、 'int'が別のスレッドによって更新された場合、Javaではそれぞれ異なるスレッドが最新の値を表示しないことがありますスレッド自体のキャッシュを持つ – Tmr

+0

一般に、これは正しいです。しかし、 'synchronized'を使用しているときに値を '更新'する必要はありません.'Synchronized'ブロックの最後に値が既に '更新'されています(これは' volatile'と 'synchronized'もう一方を冗長にする、この特定の状況では 'volatile 'は必要ありません)。 – KookieMonster

0

メソッドレベルでsynchronizedを追加しました。そのため、1つのスレッドがgetValue()を呼び出していて、1つのスレッドがsetValue()を呼び出している可能性があります。同期されたものをすべて削除して、プライベート "value"メンバーのタイプをAutomicIntegerに変更することができます。次に、そのクラス内でスレッドセーフなメソッドを使用します。

+0

メソッドに対して同期することはできません。あなたのコードがこれまでに同期できるのはオブジェクトだけです。同期されたインスタンスメソッドは 'this'オブジェクトで同期し、同期クラスメソッドはクラスオブジェクトと同期します。 –

+0

ありがとうございます。私は自分の投稿を修正しました。 –