2016-10-20 4 views
-1

このシーンをシミュレートしようとしました。配列内のスレッドは、配列内の順序として次々に実行できます。私の考えはthread[i]に常にthread[(i+1)%threads.length]を通知させることです。ロックオブジェクトとしてcurrentThreadを使用した場合にスタックする

しかし、コードがどこかで立ち往生されます。

public class OrderedOperationThreadOneLock { 

    //<- Assign tasks to workers 
    private static final String[] message1 = {"A", "D", "G"}; 
    private static final String[] message2 = {"B", "E", "H"}; 
    private static final String[] message3 = {"C", "F", "I"}; 

    private static final Task task1 = new Task(message1); 
    private static final Task task2 = new Task(message2); 
    private static final Task task3 = new Task(message3); 

    private static Thread[] workers = { 
      new Thread(task1), 
      new Thread(task2), 
      new Thread(task3) 
    }; //-> 

    private static void printThreadsStatus() { 
     for (int i = 0; i < workers.length; ++i) { 
      System.out.println("Thread: " + workers[i].getName() + 
        "\tPriority: " + workers[i].getPriority() + 
        "\tStatus: " + workers[i].getState() 
      ); 
     } 
     System.out.println("----------------------------------------------"); 
    } 


    // Print a message then wait for other thread notify. 
    // Until all the messages are outputted. 
    private static class Task implements Runnable { 
     private final String[] messages; 
     Task(String[] messages) { 
      this.messages = Arrays.copyOf(messages, messages.length); 
     } 
     @Override 
     public void run() { 
      for (int i = 0; i < messages.length; ++i) { 
       System.out.println(messages[i]); 
       Thread curThread = Thread.currentThread(); 

       // Stuck here 
       synchronized (curThread) { 
        try { 
         curThread.wait(); 
         printThreadsStatus(); 
        } catch (InterruptedException e) { 
         System.err.println("Oh, Why?"); 
        } 
       } 
       wakeUpNext(); 
      } 
     } 

     // `thread[i]` always notify `thread[(i+1)%threads.length]` 
     private void wakeUpNext() { 
      Thread curThread = Thread.currentThread(); 
      int i = 0; 
      boolean isNotified = false; 
      while (i < workers.length && !isNotified) { 
       if (curThread.getId() == workers[i].getId()) { 
        int toNotify = (i+1) % workers.length; 
        System.out.println("Worker " + i + " is waking up " + toNotify); 
        synchronized (workers[toNotify]) { 
         workers[toNotify].notify(); 
        } 
        isNotified = true; 
       } 
       i++; 
      } 
     } 
    } 

    public static void main(String... args) throws InterruptedException { 
     for (int i = 0; i < workers.length; ++i) { 
      workers[i].start(); 
      Thread.sleep(10); // Ensure start in 1,2,3 sequence 
     } 

     System.out.println("================================"); 
     printThreadsStatus(); 
     synchronized (workers[0]) { 
      workers[0].notify(); 
      printThreadsStatus(); 
     } 

     for (int i = 0; i < workers.length; ++i) { 
      workers[i].join(5000); 
      printThreadsStatus(); // All the threads are in WAITING status... 
     } 

     printThreadsStatus(); 
    } 
} 

しかし、私はモニターロックとしてTaskを使用している場合。その後、プログラムはスタックされません:

public class OrderedOperationThreadSingleLock { 
    //<- Assign tasks to workers 
    private static final String[] message1 = {"A", "D", "G"}; 
    private static final String[] message2 = {"B", "E", "H"}; 
    private static final String[] message3 = {"C", "F", "I"}; 

    private static final Task[] tasks = { 
      new Task(message1), 
      new Task(message2), 
      new Task(message3) 
    }; 

    private static Thread[] workers = { 
      new Thread(tasks[0]), 
      new Thread(tasks[1]), 
      new Thread(tasks[2]) 
    };//-> 

    private static void printThreadsStatus() { 
     for (int i = 0; i < workers.length; ++i) { 
      System.out.println("Thread: " + workers[i].getName() + 
        "\tPriority: " + workers[i].getPriority() + 
        "\tStatus: " + workers[i].getState() 
      ); 
     } 
     System.out.println("----------------------------------------------"); 
    } 


    private static class Task implements Runnable { 
     private final String[] messages; 
     Task(String[] messages) { 
      this.messages = Arrays.copyOf(messages, messages.length); 
     } 
     @Override 
     public void run() { 
      for (int i = 0; i < messages.length; ++i) { 
       System.out.println(messages[i]); 
       synchronized (this) { 
        try { 
         wait(); 
         printThreadsStatus(); 
        } catch (InterruptedException e) { 
         System.err.println("Oh, Why?"); 
        } 
       } 
       wakeUpNext(); 
      } 
     } 

     // Use task as the lock instead of thread object 
     private void wakeUpNext() { 
      int i = 0; 
      boolean isNotified = false; 
      Thread t = Thread.currentThread(); 
      while (i < workers.length && !isNotified) { 
       if (t.getId() == workers[i].getId()) { 
        int toNotify = (i+1) % workers.length; 
        System.out.println("Worker " + i + " is waking up " + toNotify); 
        synchronized (tasks[toNotify]) { 
         System.out.println("Prepare Waking up " + toNotify); 
         tasks[toNotify].notify(); 
        } 
        isNotified = true; 
       } 
       i++; 
      } 
     } 
    } 

    public static void main(String... args) throws InterruptedException { 
     for (int i = 0; i < workers.length; ++i) { 
      workers[i].start(); 
      Thread.sleep(10); 
     } 

     System.out.println("================================"); 
     printThreadsStatus(); 
     synchronized (tasks[0]) { 
      tasks[0].notify(); 
      printThreadsStatus(); 
     } 

     for (int i = 0; i < workers.length; ++i) { 
      workers[i].join(5000); 
      printThreadsStatus(); 
     } 

     printThreadsStatus(); 
    } 
} 

私の質問はなぜスレッドオブジェクトが実行され、タスクがブロックされないのですか?

答えて

1

おかしいことはworkers[i].join(5000);にあるということです。メソッドjoinもスレッド上で同期されており、waitを呼び出すので、wakeUpNextのスレッドに通知すると間違った目覚めを起こします。

それは次のように起こります。

  • メインスレッドは、スレッド1
  • スレッド1つのウェイクアップ
  • スレッド1は、通知によって、スレッド1を待機しているスレッド2
  • スレッド1人の睡眠を知らせる通知。
  • メインスレッドはメインスレッドをスリープさせるスレッド1のjoinを呼び出します。
  • ある時点で、スレッド3はスレッド1に通知しますが、何らかの理由でこの通知を待っているスレッドが2つあり(スレッド1自体とメインスレッド)、メインスレッドに通知されます。
  • メインスレッドがスリープ状態に戻って得るために偽のウェイクアップ
  • 原因である
+0

への準備ができてjoin方法は、しかし、すべてのスレッドが終了していると私は、彼らがそこまで取得しない疑いがある場合に限ります。 –

+1

答えにシナリオを追加します。 – talex

+0

非常に説得力のある、ありがとう –

0

問題を起こす可能性のあるシナリオはこのようなものです。

  • スレッド1はスレッド1
  • (スレッド1がまだ滑りされていないため、通知が失われた)スレッド2
  • スレッド2ウェイクアップ通知し、スレッド3
  • 糸3ウェイクアップ通知し、スレッド1に通知します
関連する問題