2017-01-26 5 views
0

私は、出力されるすべてのものを出力するOutputクラスを持っています。Thread.sleep blocks otherスレッド

public class Output { 
    private static List<String> textList = new ArrayList<>(); 
    private static Output output = null; 

    private Output() { 
     Runnable task =() -> { 
      int lastIndex = 0; 

      while (true) { 
       while (lastIndex < textList.size()) { 
        System.out.println(lastIndex + " - " + textList.size() + ": " + textList.get(lastIndex)); 

        outputText(textList.get(lastIndex)); 

        lastIndex ++; 
       } 
      } 
     }; 

     new Thread(task).start(); 
    } 

    private static void outputText(String text) { 
     synchronized (System.out) { 
      System.out.println(text); 
     } 

    } 

    public static void say(String text) { 
     if (output == null) { 
      output = new Output(); 
     } 

     textList.add(text); 
    } 
} 

私は印刷するために何かを追加すると、すべてが正常に動作します:

for (int i = 0; i < 10; i++) { 
    Output.say("" + i); 
} 

しかし、私はそれが最初の出力に停止したループにThread.sleepを追加します。

for (int i = 0; i < 10; i++) { 
    Output.say("" + i); 
    Thread.sleep(100); 
} 

どのようにすることができます私はそれを防ぐ?私は、唯一の主なスレッドではなく、別のスレッドをスリープ状態で停止していることを意味します。

+0

どこにループを追加しますか?どこに2番目のスレッド? –

+1

1)「say」_at all_のロジックが分からない。 'static'変数にアクセスするために' Output'のインスタンスが必要なのはなぜですか?コンストラクタでスレッドを開始することは一般的には良い考えではないことに注意してください。 2)私は 'textList'を突然変異させるところで' synchronized'ブロックを見ることはありません。 [JavaDoc for 'ArrayList'](https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList)を参照してください。html) "_ **この実装は同期されていないことに注意してください。** _ _"(emphas theirs) –

+0

最初のスレッドは通常のメインメソッドで、2番目のスレッドはOutputコンストラクタで開始します。私の誤りは、私は 'say'の' Output'のインスタンスを削除しました。私はArrayListが同期されていないことを知っていますが、私はそれを別々に解決する方法を知らない。 –

答えて

2

スレッドを正しく同期しないと、スレッドが他のスレッドによって行われた更新を参照する保証はありません。彼らは完全に更新を見逃したり、一部だけを見たりして、完全に一貫性のない結果を生むことがあります。時には彼らもが正しいことを行うためにを表示することがあります。 (スレッドセーフであると指定された有効な構造体の意味で)適切な同期がなければ、これは完全に予測できません。

場合によっては、あなたの例のように、特定の動作を見る可能性が高くなります。ほとんどの実行では、sleepのないループは、もう一方のスレッドが作業を開始する前に完了し、sleepを挿入すると、2番目のスレッドが値を見た後に更新が失われる可能性が高くなります。 2番目のスレッドがtextList.size()の値を見たら、値を永久に再利用して、lastIndex < textList.size()falseと評価し、while(true) { }と同等の値を実行します。

スレッド安全性のために構造体を挿入した唯一の場所は、outputTextで、スレッド1つでのみ呼び出されます(ほとんどの環境ではSystem.outへの印刷は内部的に同期されます)。

さらに、タイプOutputのオブジェクトを作成する理由は明らかではありません。このオブジェクトは、すべてのフィールドとメソッドがstaticであるため、ここでは関連性がありません。

、それはまだあなたの問題に無限の第二のスレッドによる終了しないともポーリングループでCPUを燃やす決して、元のコードが含まれていてもあなたのコードは

public static void main(String[] args) throws InterruptedException { 
    List<String> textList = new ArrayList<>(); 
    new Thread(() -> { 
     int index=0; 
     while(true) synchronized(textList) { 
      for(; index<textList.size(); index++) 
       System.out.println(textList.get(index)); 
     } 
    }).start(); 

    for (int i = 0; i < 10; i++) { 
     synchronized(textList) { 
      textList.add(""+i); 
     } 
     Thread.sleep(100); 
    } 
} 

に補正し、簡素化することができます。

public static void main(String[] args) throws InterruptedException { 
    List<String> textList = new ArrayList<>(); 
    new Thread(() -> { 
     synchronized(textList) { 
      for(int index=0; ; index++) { 
       while(index>=textList.size()) try { 
        textList.wait(); 
       } catch(InterruptedException ex) { return; } 
       final String item = textList.get(index); 
       if(item==null) break; 
       System.out.println(item); 
      } 
     } 
    }).start(); 

    for (int i = 0; i < 10; i++) { 
     synchronized(textList) { 
      textList.add(""+i); 
      textList.notify(); 
     } 
     Thread.sleep(100); 
    } 
    synchronized(textList) { 
     textList.add(null); 
     textList.notify(); 
    } 
} 

これはまだあなたが実際の生活のコードでは使用しないでくださいだけで学術的な例である:あなたが第二のスレッドは、新しいアイテムのためを待ち、終了条件を追加してみましょう必要があります。 Java APIによって提供されるスレッドセーフなデータ交換のためのクラスがあり、それを自分で実装する負担を取り除きます。

public static void main(String[] args) throws InterruptedException { 
    ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(10); 
    String endMarker = "END-OF-QUEUE"; // the queue does not allow null 
    new Thread(() -> { 
     for(;;) try { 
      String item = queue.take(); 
      if(item == endMarker) break;// don't use == for ordinary strings 
      System.out.println(item); 
     } catch(InterruptedException ex) { return; } 
    }).start(); 

    for (int i = 0; i < 10; i++) { 
     queue.put(""+i); 
     Thread.sleep(100); 
    } 
    queue.put(endMarker); 
} 
+0

あなたの例をありがとう。あなたは 'for(); try {...}'を説明してください。 –

+1

'for(;;)'は 'while(true)'と同じです。私は最初の例でそれを避けようとしましたが、あなたのコードのように保つようにしましたが、私はそれが最後のもので逃げ切ったことにとても慣れています。 'for(;;)'と 'while(true)'の間には本当の違いはありません。 – Holger