線程通信的目的是為了能夠讓線程之間互相發送信号。另外,線程通信還能夠使得線程等待其它線程的信号,比如,線程b可以等待線程a的信号,這個信号可以是線程a已經處理完成的信号。
有一個簡單的實作線程之間通信的方式,就是在共享對象的變量中設定信号值。比如線程a在一個同步塊中設定一個成員變量<code>hasdatatoprocess</code>值為<code>true</code>,而線程b同樣在一個同步塊中讀取這個成員變量。下面例子示範了一個持有信号值的對象,并提供了設定信号值和擷取信号值的同步方法:
threadb計算完成會在共享對象中設定信号值:
threada在循環中一直檢測共享對象的信号值,等待threadb計算完成的信号:
很明顯,采用共享對象方式通信的線程a和線程b必須持有同一個<code>mysignal</code>對象的引用,這樣它們才能彼此檢測到對方設定的信号。當然,信号也可存儲在共享記憶體buffer中,它和執行個體是分開的。
從上面例子中可以看出,線程a一直在等待資料就緒,或者說線程a一直在等待線程b設定<code>hasdatatoprocess</code>的信号值為true:
為什麼說是忙等呢?因為上面代碼一直在執行循環,直到<code>hasdatatoprocess</code>被設定為true。
忙等意味着線程還處于運作狀态,一直在消耗cpu資源,是以,忙等不是一種很好的現象。那麼能不能讓線程在等待信号時釋放cpu資源進入阻塞狀态呢?其實<code>java.lang.object</code>提供的wait()、notify()、notifyall()方法就可以解決忙等問題。
java提供了一種内聯機制可以讓線程在等待信号時進入非運作狀态。當一個線程調用任何對象上的wait()方法時便會進入非運作狀态,直到另一個線程調用同一個對象上的notify()或notifyall()方法。
為了能夠調用一個對象的wait()、notify()方法,調用線程必須先獲得這個對象的鎖。因為線程隻有在同步塊中才會占用對象的鎖,是以線程必須在同步塊中調用wait()、notify()方法。
我們把上面通過共享對象通信的例子改成調用對象wait()、notify()方法來實作:
首先我們先構造一個任意對象,我們又把它稱作監控對象:
threadd負責計算,在計算完成時喚醒被阻塞的threadc:
threadc等待threadd的喚醒:
在這個例子中,線程c因調用了監控對象的wait()方法而挂起,線程d通過調用監控對象的notify()方法喚醒挂起的線程c。我們還可以看到,兩個線程都是在同步塊中調用的wait()和notify()方法。如果一個線程在沒有獲得對象鎖的前提下調用了這個對象的wait()或notify()方法,方法調用時将會抛出 <code>illegalmonitorstateexception</code>異常。
注意,當一個線程調用一個對象的notify()方法,則會喚醒正在等待這個對象所有線程中的一個線程(喚醒的線程是随機的),當線程調用的是對象的notifyall()方法,則會喚醒所有等待這個對象的線程(喚醒的所有線程中哪一個會執行也是不确定的)。
這裡還有一個問題,既然調用對象wait()方法的線程需要獲得這個對象的鎖,那麼這會不會阻塞其它線程調用這個對象的notify()方法呢?答案是不會阻塞,當一個線程調用監控對象的wait()方法時,它便會釋放掉這個監控對象鎖,以便讓其它線程能夠調用這個對象的notify()方法或者wait()方法。
另外,當一個線程被喚醒時不會立刻退出wait()方法,隻有當調用notify()的線程退出它的同步塊為止。也就是說,被喚醒的線程隻有重新獲得監控對象鎖時才會退出wait()方法,因為wait()方法在同步塊中,它的執行需要再次獲得對象鎖。是以,當通過notifyall()方法喚醒被阻塞的線程時,一次隻能有一個線程會退出wait()方法,同樣是因為每個線程都需要先獲得監控對象鎖才能執行同步塊中的wait()方法退出。