2016-07-03 5 views
3

コードは次のとおりです。基本的に、ReadCalculationクラスとCalculatorクラスをRunnableを実装する代わりにThreadを拡張するように変更した場合、これらのクラスをインスタンス化して新しいスレッドオブジェクトに渡すか、start()を呼び出す必要があります。拡張する代わりにRunnableを実装するときの動作が異なる

Calculator calc = new Calculator(); 
new ReadCalculation(calc).start(); 
new ReadCalculation(calc).start(); 
calc.start(); 

これまで何も特別..しかし、あなたは、この小さなプログラムを実行すると、あなたのスレッドは、「計算のために待っています...」ブロックされたままになることを我々は拡張の上にRunnableを実装上つもりなら、巨大なチャンスがありますThreadクラス

Runnableを実装する代わりにThreadクラスを拡張している場合、競合条件の兆候がなくても動作は正しいです。 この現象の原因となる可能性のあるアイデアはありますか?あなたがwait()を実行すると

public class NotifyAllAndWait { 

public static void main(String[] args) { 

     Calculator calc = new Calculator(); 
     Thread th01 = new Thread(new ReadCalculation(calc)); 
     th01.start(); 
     Thread th02 = new Thread(new ReadCalculation(calc)); 
     th02.start(); 

     Thread calcThread = new Thread(calc); 
     calcThread.start(); 
    } 
} 

class ReadCalculation implements Runnable { 

    private Calculator calc = null; 
    ReadCalculation(Calculator calc) { 
     this.calc = calc; 
    } 

    @Override 
    public void run() { 
     synchronized (calc) { 
      try { 
       System.out.println(Thread.currentThread().getName() + " Waiting for calculation..."); 
       calc.wait(); 
      } catch (InterruptedException e) { 
       e.printStackTrace(); 
      } 
      System.out.println(Thread.currentThread().getName() + " Total: " + calc.getTotal()); 
     } 
    } 
} 

class Calculator implements Runnable { 
    private int total = 0; 
    @Override 
    public void run() { 
     synchronized(this) { 
      System.out.println(Thread.currentThread().getName() + " RUNNING CALCULATION!"); 
      for(int i = 0; i < 100; i = i + 2){ 
       total = total + i; 
      } 
      notifyAll(); 
     } 
    } 
    public int getTotal() { 
     return total; 
    } 
} 
+0

私は言語やJVMの仕様の一部を引用することはできませんが、実際にはそうである可能性がありますが、実際には 'Thread'のソースコードは両方のスレッドで非常に多くのロックを必要とします。 class'と現在の 'Thread'インスタンスです。 'calc'の内側と外側の両方からCalculatorインスタンスをロックします。これらのロックは、専用のターゲットRunnableを使用するときに 'Thread'の内部でロックするのを妨げないかもしれません(Androidスレッド実装のコードは渡されたRunnableでロックされないようです)。 – user1643723

答えて

2

、少なくとも、あなたはCalculatorスレッドが​​ブロックに入る前にReadCalculationスレッドがwait()に到達することを確実にするために何もしません。 Calculatorスレッドが最初に​​ブロックに入ると、というスレッドがReadCalculationスレッドより先に呼び出される前に、notifyAll()が呼び出されます。そしてそれが起こるならば、notifyAll()はノーオペレーションであり、ReadCalculationスレッドは永遠に待つでしょう。 (notifyAll()はオブジェクトのみで待機している、すでにあるスレッドを気にするためです。それはwait()後続の呼び出しに検出することができたオブジェクト上のインジケータの任意の並べ替えを設定しません。)

という問題を解決するには、あなたは行わネスをチェックするために使用することができるCalculatorにプロパティを追加し、Calculatorない行われている場合にのみwait()を呼び出すことができます。

if(! calc.isCalculationDone()) { 
    calc.wait(); 
} 

(なお、完全に避けるために、競合状態は、それが全体if内部​​ブロックすること、およびnotifyAll()を呼び出しCalculatorセットこのプロパティを内部​​ブロックという-statementすることが重要です。理由は分かりますか?)

(ちなみにPeter Lawreyのコメントは、「他のスレッドが始まる前に100回繰り返しても簡単にスレッドを開始できます」というコメントは非常に誤解を招きます。プログラムでは100回の繰り返しがすべて発生しますCalculatorが​​ブロックに入った後。ReadCalculationスレッドは、​​ブロックをブロックし、calc.wait()を呼び出してブロックするので、Calculatorブロックは​​ブロックにあります。前のプログラムのタイミングそのポイント。)


あなたは全体extends Threadバージョンを投稿していないが、私はそれがどのように見えるかを正しく理解すれば、それは実際にはまだ同じ競合状態を持っています。しかし、マイナーな変更が不正行為の可能性に大きく影響する可能性がある競合状態の性質上です。たとえあなたがプログラムを十分な時間実行すると、時折誤動作することがほぼ確実であるため、実際に不正行為をしていないと思われる場合でも、レースの状態を修正する必要があります。

1つのアプローチで他のアプローチよりも誤操作が頻繁に発生するように見える理由については説明がありません。上記のuser1643723のコメントのように、extends Threadというアプローチは、あなたのCalculatorインスタンスで多くのコードotherがロックされる可能性があることを意味します。これはある種の効果をもたらす可能性があります。しかし、正直言って、私は競争状態がより頻繁に、またはそれほど頻繁に悪行を引き起こす理由についてあまり心配する価値があるとは思わない。それにかかわらず、私たちはそれを修正しなければなりません。


ところで:上記

  • は、私がif(! calc.isCalculationDone())を使用しました。実際にはwait()のコールを常にwhile -loopにラップするのがベストプラクティスです。実際にはと書くべきです。これには2つの大きな理由があります:自明でないプログラムで

    • が、あなたは必ずしもなぜnotifyAll()が呼び出された、またはあなたが行う場合でも、あなたはその理由がまだによって適用されるかどうかわからないかわかりません待っているスレッドが実際に目を覚まして、​​- ロックを回復した時刻。 wait()を書くのではなく、wait_until_ready_to_proceed()のアイデアを表現するためにwhile(not_ready_to_proceed()) { wait(); }構造を使用している場合は、notify()/wait()の対話の正当性を判断するのがずっと簡単になり、準備ができていない。

    • 一部のオペレーティングシステムでは、プロセスに信号を送信すると、wait()というスレッドがすべて起動します。これはと呼ばれ、偽のウェークアップと呼ばれます。詳細については、"Do spurious wakeups actually happen?"を参照してください。したがって、notify()またはnotifyAll()と呼ばれる他のスレッドがなくても、スレッドがウェイクアップする可能性があります。

  • Calculator.run()for -loopは、それがどの同期を必要としないので、​​ブロックにすべきではないので、競合は必要ありません。あなたの小さなプログラムでは、実際には違いはありません(なぜなら、実際に他のスレッドはその時点で何もしないので)。しかし、ベストプラクティスは、常に​​ブロック内のコード量を最小限に抑えることです。

2

これはあなたがnotify()ブロックを行った状態変更後のループにする必要があります。例えば

// when notify 
changed = true; 
x.notifyAll(); 

// when waiting 
while(!changed) 
    x.wait(); 

これを行わないと、偽の目覚めや紛失などの問題が発生します。

注:スレッドは、他のスレッドが開始される前でも簡単に100回繰り返すことができます。 Threadオブジェクトをあらかじめ作成することで、あなたの場合の結果を変更するのに十分なパフォーマンスが得られる可能性があります。 implements Runnableバージョンで

+1

できるだけ正確に、最初のステートメントはここでは無関係に見えます。 – user1643723

+0

@ user1643723 OPの問題を説明するより良い回答で訂正されてうれしいです。 –

+0

@PeterLawreyは「紛失したことを知らせる」という部分について詳しく説明していますか? [JLSのnotify()の章(https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.2.2)には、そのようなことは言及されていません。 – user1643723

関連する問題