Java 多線程編程之六:線程之間的通訊(附原始碼)
原始碼下載
多線程編程中,線程之間的通訊是一個比較複雜的問題。大家往往搞不懂什麼是競爭資源?什麼時候考慮同步?怎麼進行同步?什麼是線程通訊?怎麼進行通訊?很多朋友面試的時候都遇到過這樣類似的一個編程題:給定一個情境,編寫代碼來恰當使用等待、通知和通知所有線程。相信很多朋友對java.lang.Object類的這三個方法都很熟悉,notify、notifyAll、wait,但是真正能運用自如的卻不多。因此面試中掛在上面那個面試題上的朋友還真不在少數。本文列舉了一個經典的生產者、消費者原始碼,以代碼的角度來說明這個問題,並附加樣本原始碼,相信讀者看過之後對線程之間的通訊互動會有更進一步的認識!
生產者-消費者模型,堪稱多線程程式中的經典。本源碼中將使用 java.lang.Object 的 wait、notify、notifyAll 來實現這個模型,這才是最重要的。
開始以前,讓我們先來熟悉一下生產者-消費者模型的遊戲規則:
1、倉滿不能生產;
2、藏空不能消費;
3、消費者消費產品前發現不能滿足此次消費後通知生產者進行生產;
4、生產者生產出產品後通知消費者進行消費。
好的。開始之前再來回顧一下對象鎖的概念……這是最關鍵的。每個對象都有一個內建鎖。當程式運行到非靜態 synchronized 方法上時,將自動獲得與正在執行代碼類的當前執行個體(即 this 執行個體)有關的鎖。java.lang.Thread.sleep() 方法執行時並不釋放此鎖;java.lang.Object.wait() 方法執行時釋放此鎖。好了,就到這裡吧,說太多了一來顯得作者羅嗦,二來也有侮辱讀者 Java 基礎的嫌疑。開始代碼示範。
生產者-消費者模型-倉庫原始碼
package com.defonds.thread;<br />/**<br /> *<br /> *<br /> * 項目名稱:ThreadApp<br /> * 類名稱:Godown<br /> * 類描述:生產者-消費者模型之倉庫<br /> * 建立人:Defonds<br /> * 建立時間:2010-1-26 上午10:50:00<br /> * 修改人:Defonds<br /> * 修改時間:2010-1-26 上午10:50:00<br /> * 修改備忘:<br /> * @version<br /> *<br /> */<br />public class Godown {</p><p>private final int max_size = 100;//最大庫存容量<br />private int curNum;//現有庫存量</p><p>/**<br /> *<br /> * 建立一個新的執行個體 Godown.<br /> *<br /> * @param curNum<br /> */<br />public Godown(int curNum){<br />this.curNum = curNum;<br />}</p><p>/**<br /> * 生產指定數目的產品<br /> */<br />public synchronized void produce(int needNum){<br />while(true){<br />/**<br /> * 如果不需要生產,進入等待狀態<br /> */<br />while(this.curNum + needNum > this.max_size){<br />System.out.println(Thread.currentThread().getName() + "要生產的產品數量" + needNum + "已經超出剩餘庫存容量" + (this.max_size - this.curNum) +",暫時不能進行生產任務!");<br />try {<br />this.wait();<br />} catch (InterruptedException e) {<br />// TODO Auto-generated catch block<br />e.printStackTrace();<br />}<br />}</p><p>/**<br /> * 滿足了生產條件,進行產品生產<br /> */<br />this.curNum += needNum;<br />System.out.println(Thread.currentThread().getName() + "已經生產了" + needNum + ",現存庫存量是為:" + this.curNum);</p><p>/**<br /> * 喚醒在此對象監視器上等待的所有線程<br /> */<br />this.notifyAll();<br />}<br />}</p><p>/**<br /> * 消費指定數目的產品<br /> */<br />public synchronized void consume(int needNum){<br />while(true){<br />/**<br /> * 如果不可以消費,進入等待狀態<br /> */<br />while(this.curNum < needNum){<br />System.out.println(Thread.currentThread().getName() + "要消費的產品數量" + needNum + "已經超出剩餘庫存量" + this.curNum + ",暫時不能進行消費!");<br />try {<br />this.wait();<br />} catch (InterruptedException e) {<br />// TODO Auto-generated catch block<br />e.printStackTrace();<br />}<br />}</p><p>/**<br /> * 滿足了消費條件,進行產品消費<br /> */<br />this.curNum -= needNum;<br />System.out.println(Thread.currentThread().getName() + "已經消費了" + needNum + ",現存庫存量是為:" + this.curNum);</p><p>/**<br /> * 喚醒在此對象監視器上等待的所有線程<br /> */<br />this.notifyAll();<br />}<br />}<br />}<br />
生產者-消費者模型-生產者原始碼
package com.defonds.thread;<br />/**<br /> *<br /> *<br /> * 項目名稱:ThreadApp<br /> * 類名稱:Producer<br /> * 類描述:生產者-消費者模型之生產者<br /> * 建立人:Defonds<br /> * 建立時間:2010-1-26 上午10:45:30<br /> * 修改人:Defonds<br /> * 修改時間:2010-1-26 上午10:45:30<br /> * 修改備忘:<br /> * @version<br /> *<br /> */<br />public class Producer extends Thread{</p><p>private int needNum;//每次要生產產品的數量<br />private Godown godown;//倉庫</p><p>/**<br /> *<br /> * 建立一個新的執行個體 Producer.<br /> *<br /> * @param needNum<br /> * @param godown<br /> */<br />public Producer(int needNum,Godown godown){<br />this.needNum = needNum;<br />this.godown = godown;<br />}</p><p>/**<br /> * 重寫 java.lang.Thread 的 run 方法<br /> */<br />public void run(){<br />this.godown.produce(this.needNum);<br />}<br />}<br />
生產者-消費者模型-消費者原始碼
package com.defonds.thread;<br />/**<br /> *<br /> *<br /> * 項目名稱:ThreadApp<br /> * 類名稱:Consumer<br /> * 類描述:生產者-消費者模型之消費者<br /> * 建立人:Defonds<br /> * 建立時間:2010-1-26 上午10:50:48<br /> * 修改人:Defonds<br /> * 修改時間:2010-1-26 上午10:50:48<br /> * 修改備忘:<br /> * @version<br /> *<br /> */<br />public class Consumer extends Thread{</p><p>private int needNum;//每次要消費產品的數量<br />private Godown godown;//倉庫</p><p>/**<br /> *<br /> * 建立一個新的執行個體 Consumer.<br /> *<br /> * @param needNum<br /> * @param godown<br /> */<br />public Consumer(int needNum,Godown godown){<br />this.needNum = needNum;<br />this.godown = godown;<br />}</p><p>/**<br /> * 重寫 java.lang.Thread 的 run 方法<br /> */<br />public void run(){<br />this.godown.consume(this.needNum);<br />}<br />}<br />
生產者-消費者模型-程式入口原始碼
package com.defonds.thread;<br />/**<br /> *<br /> *<br /> * 項目名稱:ThreadApp<br /> * 類名稱:ThreadApp<br /> * 類描述:生產者-消費者模型之程式入口<br /> * 建立人:Defonds<br /> * 建立時間:2010-1-26 上午10:51:15<br /> * 修改人:Defonds<br /> * 修改時間:2010-1-26 上午10:51:15<br /> * 修改備忘:<br /> * @version<br /> *<br /> */<br />public class ThreadApp {<br />public static void main(String[] args) {</p><p>/**<br /> * 倉庫初始化<br /> */<br />Godown godown = new Godown(30);</p><p>/**<br /> * 消費者初始化<br /> */<br />Consumer consumer0 = new Consumer(30,godown);<br />Consumer consumer1 = new Consumer(20,godown);<br />Consumer consumer2 = new Consumer(40,godown);</p><p>/**<br /> * 生產者初始化<br /> */<br />Producer producer0 = new Producer(10,godown);<br />Producer producer1 = new Producer(10,godown);<br />Producer producer2 = new Producer(10,godown);<br />Producer producer3 = new Producer(10,godown);<br />Producer producer4 = new Producer(10,godown);<br />Producer producer5 = new Producer(10,godown);<br />Producer producer6 = new Producer(10,godown);<br />Producer producer7 = new Producer(10,godown);</p><p>/**<br /> * 標記每個生產者/消費者<br /> */<br />consumer0.setName("consumer0");<br />consumer1.setName("consumer1");<br />consumer2.setName("consumer2");<br />producer0.setName("producer0");<br />producer1.setName("producer1");<br />producer2.setName("producer2");<br />producer3.setName("producer3");<br />producer4.setName("producer4");<br />producer5.setName("producer5");<br />producer6.setName("producer6");<br />producer7.setName("producer7");</p><p>/**<br /> * 開始進行生產-消費<br /> */<br />consumer0.start();<br />consumer1.start();<br />consumer2.start();<br />producer0.start();<br />producer1.start();<br />producer2.start();<br />producer3.start();<br />producer4.start();<br />producer5.start();<br />producer6.start();<br />producer7.start();<br />}<br />}<br />
一次執行本代碼時的控制台輸出片段:
consumer2已經消費了40,現存庫存量是為:20
consumer2要消費的產品數量40已經超出剩餘庫存量20,暫時不能進行消費!
consumer0要消費的產品數量30已經超出剩餘庫存量20,暫時不能進行消費!
consumer1已經消費了20,現存庫存量是為:0
consumer1要消費的產品數量20已經超出剩餘庫存量0,暫時不能進行消費!
consumer0要消費的產品數量30已經超出剩餘庫存量0,暫時不能進行消費!
consumer2要消費的產品數量40已經超出剩餘庫存量0,暫時不能進行消費!
producer5已經生產了10,現存庫存量是為:10
producer5已經生產了10,現存庫存量是為:20
producer5已經生產了10,現存庫存量是為:30
producer5已經生產了10,現存庫存量是為:40
producer5已經生產了10,現存庫存量是為:50
producer5已經生產了10,現存庫存量是為:60
producer5已經生產了10,現存庫存量是為:70
producer5已經生產了10,現存庫存量是為:80
producer5已經生產了10,現存庫存量是為:90
producer5已經生產了10,現存庫存量是為:100
producer5要生產的產品數量10已經超出剩餘庫存容量0,暫時不能進行生產任務!
producer1要生產的產品數量10已經超出剩餘庫存容量0,暫時不能進行生產任務!
producer0要生產的產品數量10已經超出剩餘庫存容量0,暫時不能進行生產任務!
producer2要生產的產品數量10已經超出剩餘庫存容量0,暫時不能進行生產任務!
producer4要生產的產品數量10已經超出剩餘庫存容量0,暫時不能進行生產任務!
producer3要生產的產品數量10已經超出剩餘庫存容量0,暫時不能進行生產任務!
producer6要生產的產品數量10已經超出剩餘庫存容量0,暫時不能進行生產任務!
producer7要生產的產品數量10已經超出剩餘庫存容量0,暫時不能進行生產任務!
consumer2已經消費了40,現存庫存量是為:60
consumer2已經消費了40,現存庫存量是為:20
consumer2要消費的產品數量40已經超出剩餘庫存量20,暫時不能進行消費!
consumer0要消費的產品數量30已經超出剩餘庫存量20,暫時不能進行消費!
consumer1已經消費了20,現存庫存量是為:0
consumer1要消費的產品數量20已經超出剩餘庫
當然,本程式還存在一點問題,那就是一旦某個生產者得到 CPU 資源後,生產出一次產品後,儘管使用 notifyAll,但是卻並沒有讓出 CPU 資源,以至於其他生產者無法再得到 CPU 資源;消費者也是如此。相信聰明的讀者會找到更佳的辦法來解決這個問題。但本例子主要是用來給大家示範線程之間的互動,就這一點需求來說,本例子足夠了。