1. 程式人生 > >七. 多線程編程9.線程間通信

七. 多線程編程9.線程間通信

程序 tro 討論 結束 ava 排隊 被調用 clas www

上述例題無條件的阻塞了其他線程異步訪問某個方法。Java對象中隱式管程的應用是很強大的,但是你可以通過進程間通信達到更微妙的境界。這在Java中是尤為簡單的。

像前面所討論過的,多線程通過把任務分成離散的和合乎邏輯的單元代替了事件循環程序。線程還有第二優點:它遠離了輪詢。輪詢通常由重復監測條件的循環實現。一旦條件成立,就要采取適當的行動。這浪費了CPU時間。舉例來說,考慮經典的序列問題,當一個線程正在產生數據而另一個程序正在消費它。為使問題變得更有趣,假設數據產生器必須等待消費者完成工作才能產生新的數據。在輪詢系統,消費者在等待生產者產生數據時會浪費很多CPU周期。一旦生產者完成工作,它將啟動輪詢,浪費更多的CPU時間等待消費者的工作結束,如此下去。很明顯,這種情形不受歡迎。

為避免輪詢,Java包含了通過wait( ),notify( )和notifyAll( )方法實現的一個進程間通信機制。這些方法在對象中是用final方法實現的,所以所有的類都含有它們。這三個方法僅在synchronized方法中才能被調用。盡管這些方法從計算機科學遠景方向上來說具有概念的高度先進性,實際中用起來是很簡單的:

  • wait( ) 告知被調用的線程放棄管程進入睡眠直到其他線程進入相同管程並且調用notify( )。
  • notify( ) 恢復相同對象中第一個調用 wait( ) 的線程。
  • notifyAll( ) 恢復相同對象中所有調用 wait( ) 的線程。具有最高優先級的線程最先運行。


這些方法在Object中被聲明,如下所示:
final void wait( ) throws InterruptedException
final void notify( )
final void notifyAll( )
wait( )存在的另外的形式允許你定義等待時間。

下面的例子程序錯誤的實行了一個簡單生產者/消費者的問題。它由四個類組成:Q,設法獲得同步的序列;Producer,產生排隊的線程對象;Consumer,消費序列的線程對象;以及PC,創建單個Q,Producer,和Consumer的小類。
// An incorrect implementation of a producer and consumer.
class Q {
int n;
synchronized int get() {
System.out.println("Got: " + n);
return n;
}
synchronized void put(int n) {
this.n = n;
System.out.println("Put: " + n);
}
}
class Producer implements Runnable {
Q q;
Producer(Q q) {
this.q = q;
new Thread(this, "Producer").start();
}
public void run() {
int i = 0;
while(true) {
q.put(i++);
}
}
}
class Consumer implements Runnable {
Q q;
Consumer(Q q) {
this.q = q;
new Thread(this, "Consumer").start();
}
public void run() {
while(true) {
q.get();
}
}
}
class PC {
public static void main(String args[]) {
Q q = new Q();
new Producer(q);
new Consumer(q);
System.out.println("Press Control-C to stop.");
}
}
盡管Q類中的put( )和get( )方法是同步的,沒有東西阻止生產者超越消費者,也沒有東西阻止消費者消費同樣的序列兩次。這樣,你就得到下面的錯誤輸出(輸出將隨處理器速度和裝載的任務而改變):
Put: 1
Got: 1
Got: 1
Got: 1
Got: 1
Got: 1
Put: 2
Put: 3
Put: 4
Put: 5
Put: 6
Put: 7
Got: 7
生產者生成1後,消費者依次獲得同樣的1五次。生產者在繼續生成2到7,消費者沒有機會獲得它們。

用Java正確的編寫該程序是用wait( )和notify( )來對兩個方向進行標誌,如下所示:
// A correct implementation of a producer and consumer.
class Q {
int n;
boolean valueSet = false www.yigouyule2.cc ;
synchronized int get() {
if(!valueSet)
try {
wait();
} catch(InterruptedException e) {
System.out.println("InterruptedException caught");
}
System.out.println("Got: " + n);
valueSet = false;
notify();
return n;
}
synchronized void put(int n) {
if(valueSet)
try {
wait();
} catch(InterruptedException e) {
System.out.println("InterruptedException caught");
}
this.n = n;
valueSet = true;
System.out.println("Put: " + n);
notify();
}
}
class Producer implements Runnable {
Q q;
Producer(Q q) {
this.q = q;
new Thread(this, "Producer").start();
}
public void run() {
int i = 0;
while(true) {
q.put(i++);
}
}
}
class Consumer implements Runnable {
Q q;
Consumer(Q q) {
this.q = q;
new Thread(this, "Consumer").start();
}
public void run() {
while(true) {
q.get();
}
}
}
class PCFixed {
public static void main(String args[]) {
Q q = new Q();
new Producer(q);
new Consumer(q);
System.out.println("Press Control-C to stop.");
}
}
內部get( ), wait( )被調用。這使執行掛起直到Producer 告知數據已經預備好。這時,內部get( ) 被恢復執行。獲取數據後,get( )調用notify( )。這告訴Producer可以向序列中輸入更多數據。在put( )內,wait( )掛起執行直到Consumer取走了序列中的項目。當執行再繼續,下一個數據項目被放入序列,notify( )被調用,這通知Consumer它應該移走該數據。

下面是該程序的輸出,它清楚的顯示了同步行為:
Put: 1
Got: 1
Put: 2
Got: 2
Put: 3
Got: 3
Put: 4
Got: 4
Put: 5
Got: 5

七. 多線程編程9.線程間通信