2017-04-08 19 views
2

如何在線程間通信中調用特定的線程?如何通知Java中的特定線程

在下面的程序我有兩個線程t1t2

當我打電話t1.notify()它提出:

Exception in thread "Thread-1" java.lang.IllegalMonitorStateException 
    at java.lang.Object.notify(Native Method) 
    at Shared.methodTwo(NotifyThread.java:43) 
    at Thread2.run(NotifyThread.java:77) 
Error 

class Shared { 

    Thread1 t1 ; 
    Thread2 t2 ; 

    void ThreadInit(Thread1 t1 , Thread2 t2) { 
     this.t1 = t1 ; 
     this.t2 = t2 ; 
    } 

    synchronized void methodOne() 
    { 
     Thread t = Thread.currentThread(); 

     System.out.println(t.getName()+" is relasing the lock and going to wait"); 

     try 
     { 
      wait();  //releases the lock of this object and waits 
     } 
     catch (InterruptedException e) 
     { 
      e.printStackTrace(); 
     } 

     System.out.println(t.getName()+" got the object lock back and can continue with it's execution"); 
    } 

    synchronized void methodTwo() 
    { 
     Thread t = Thread.currentThread(); 

     try 
     { 
      Thread.sleep(5000); 
     } 
     catch (InterruptedException e) 
     { 
      e.printStackTrace(); 
     } 

     t1.notify();  

     System.out.println("A thread which is waiting for lock of this object is notified by "+t.getName()); 
    } 
    } 

    class Thread1 extends Thread 
    { 
    Shared s ; 
    Thread1(Shared s) { 

     this.s = s ; 
    } 

    public void run() 
      { 
       s.methodOne(); //t1 calling methodOne() of 's' object 
      } 

    } 

    class Thread2 extends Thread { 
     Shared s ; 
    Thread2(Shared s) { 

     this.s = s ; 

    } 

    public void run() 
      { 
       s.methodTwo(); //t1 calling methodOne() of 's' object 
      } 


    } 
    public class NotifyThread 
    { 
    public static void main(String[] args) 
    { 
     final Shared s = new Shared(); 

     Thread1 t1 = new Thread1(s) ; 
     Thread2 t2 = new Thread2(s) ; 

     s.ThreadInit(t1,t2) ; 

     t1.start(); 
     t2.start(); 
    } 
} 

回答

3

你不通知的特定線程。您調用鎖對象的通知。這喚醒了一個等待鎖的線程。 (在你的情況下,鎖定對象是一個主題...這相當迷惑圖片。不過,見下文)。

但你的問題(在IllegalMonitorStateException)發生,因爲線程執行的通知(即當前線程)不保持鎖定。這是一個(硬)要求,當前線程通知鎖定時必須保持該鎖定。

詳情,請閱讀的Javadoc Object.wait(timeout)或(例如)這樣的:http://howtodoinjava.com/core-java/multi-threading/how-to-work-with-wait-notify-and-notifyall-in-java/


我不會用一個Thread對象作爲加鎖對象。它可能會工作,但也有可能其他的東西(可能是運行時系統中的某些東西)也鎖定/等待/通知Thread對象。然後事情會變得非常混亂。

最好是專門創建鎖定對象用於此目的;例如

private final Object lock = new Object(); 

此外,編寫擴展Thread類通常是一個壞主意。實現Runnable接口通常更好,實例化它並將實例作爲參數傳遞給Thread構造函數;例如實施Runnable而不是擴展Thread

Thread t = new Thread(new Runnable() { 
    public void run() { 
     System.out.println("Hello world"); 
    }}); 
t.start(); 

一個好處是,你可以使用一些管理線程生命週期爲您更方便地使用你的代碼;例如一個ExecutorService,一個fork-join線程池或一個經典線程池。

中的第二個是,輕量線程邏輯可以簡明地實現爲一個匿名類...如在我的例子。

+0

但你不通知該線程。你在鎖上調用通知,它將通知委託給os調度程序,調度程序決定通知等待集中的哪個線程。我知道你知道的,但你所選擇的措詞似乎沒有說明什麼線程得到通知不是線程調用通知。這可能是OP所困惑的。 –

+0

更好嗎? –

+0

而這一切都說(這是相當不錯的建議)我不會使用等待/通知。我將使用'java.util.concurrent'包中的一些類:'Lock','Semaphore'和'CyclicBarrier' [所有更好的賭注](http://winterbe.com/posts/2015/04/30/java8-concurrency-tutorial-synchronized-locks-examples /),而不是嘗試自己管理同步。 – markspace

1

要添加點;

你的代碼是用內在鎖。 JVM中的每個對象都有自己的鎖。 此鎖與對象的功能無關。通過自己獲取鎖定沒有任何效果(在沒有進一步的措施(如使用synchronized關鍵字)的情況下),以防止其他線程與對象的內容混淆。在線程上調用通知並不意味着該特定的線程將收到通知。

正如前面說上獲取Thread對象的鎖被勸阻。 Thread上的join方法使用連接到的線程上的內部鎖。如果代碼因爲不同的原因而獲得了鎖,那麼可以通知線程一些它可能不關心的條件。

內部鎖是一箇中介,告訴OS調度器哪些線程正在等待。操作系統調度程序決定爲鎖設置的等待中的哪些線程得到通知。當一個線程調用一個對象的通知時,它告訴該對象的鎖定告訴調度器選擇通知哪一個等待線程。鎖知道哪些線程正在等待,但不知道他們正在等待什麼狀態。 (ReentrantLock是一個很大的改進,請參閱條件的API文檔。)

當然,notifyAll會喚醒等待集中的所有線程,但這又是鎖和調度程序知道的。調用notifyAll的線程不知道線程在等待什麼。該系統是故意設計的,以便線程不能直接通知其他線程。

這裏的另一件事是,調用等待而不檢查一個條件是不可靠的。如果一個線程在發出通知之前沒有發生鎖定,它會錯過該通知。如果一個線程收到一個通知,這並不能保證它會接下來的鎖定,另一個線程可能會採取行動並使通知線程期望的狀態無效。總是有一些內部狀態,當前線程可以檢查以確認對象的狀態是線程期望的狀態,並將該檢查作爲循環中的測試。

例如,如果我有一個固定大小的阻塞隊列(在內部使用列表實現,使用同步保護它免受併發訪問),其中線程嘗試從隊列中取出某些內容,但是如果隊列爲空則阻止take方法看起來像:

public synchronized T take() throws InterruptedException { 
    while (list.isEmpty()) { 
     wait(); 
    } 
    notifyAll(); 
    return list.remove(0); 
} 

一旦等待線程喚醒和重新獲取鎖,它會檢查,看是否目前的情況是它一直在等待。只有當線程退出循環並繼續時,情況就是如此。