執行緒通訊-等待和喚醒機制和鎖(Lock)機制
生產者和消費者案例分析:
經典的生產者和消費者案例(Producer/Consumer):
分析案例:
1):生產者和消費者應該操作共享的資源(實現方式來做).
2):使用一個或多個執行緒來表示生產者(Producer).
3):使用一個或多個執行緒來表示消費者(Consumer).
生產者消費者的示意圖:
為什麼生產者不直接把資料給消費者,而是先把資料儲存到共享資源中,然後,消費者再從共享資源中取出資料,再消費.
在這裡體現了面向物件的設計理念:低耦合.
高(緊)耦合: 直接使用生產者把肉包子給消費者
低(鬆)耦合:使用一箇中間物件,遮蔽了生產者和消費者直接的資料互動. 例子:主機板和獨立顯示卡.
高(緊)耦合:
//生產者
public class Producer{
private Consumer con;//消費者物件
}
//消費者
public class Consumer{
private Producer pro;//消費者物件
}
低(鬆)耦合:
//共享資源 public class ShareResource{ } //生產者 public class Producer{ private ShareResource resource;//共享資源物件 } //消費者 public class Consumer{ private ShareResource resource;//共享資源物件 }
實現生產者和消費者案例
生產者:
//生產者生產資料 public class Producer implements Runnable{ private ShareResource resource = null; public Producer(ShareResource resource){ this.resource = resource; } public void run() { for(int i = 0; i < 50; i++){ if(i % 2 == 0){ resource.push("春哥", "男"); } else{ resource.push("鳳姐", "女"); } } } }
消費者:
//消費者消費類
public class Consumer implements Runnable{
private ShareResource resource = null;
public Consumer(ShareResource resource){
this.resource = resource;
}
public void run() {
for(int i = 0; i < 50; i ++){
resource.popup();
}
}
}
共享資源類:
//生產者和消費者共同的資源物件
public class ShareResource {
private String name;
private String gender; //一般用列舉來寫性別,這裡先用String
/**
* 生產者向共享資源儲存資料
* @param name 生產者生產的姓名
* @param gender 生產者生產的性別
*/
public void push(String name,String gender){
this.name = name;
Thread.sleep(10);
this.gender = gender;
}
/**
* 消費者向共享資源獲取資料
*/
public void popup(){
System.out.println(this.name + "-" + this.gender);
}
}
分析生產者和消費者案例存在的問題:
建議在生產姓名和性別之間以及在列印之前使用Thread.sleep(10);使效果更明顯.
此時出現下面的情況:
問題1:出現姓別紊亂,即出現鳳姐-男的情況.
生產者先生產出春哥哥-男,此時消費者沒有消費,生產者繼續生產出姓名為鳳姐,此時消費者開始消費了.
解決方案:只要保證在生產姓名和性別的過程保持同步,中間不能被消費者執行緒進來取走資料.
可以使用同步程式碼塊/同步方法/Lock機制來保持同步性.
問題2:重複出現鳳姐-男和鳳姐-女,應該出現生產一個數據,消費一個數據.
應該交替出現: 春哥哥-男-->鳳姐-女-->春哥哥-男-->鳳姐-女.....
解決方案: 得使用 等待和喚醒機制.
執行緒通訊:不同的執行緒執行不同的任務,如果這些任務有某種關係,執行緒之間必須能夠通訊,協調完成工作.
等待和喚醒機制:
執行緒通訊-wait和notify方法介紹:
java.lang.Object類提供類兩類用於操作執行緒通訊的方法.
wait():執行該方法的執行緒物件釋放同步鎖,JVM把該執行緒存放到等待池中,等待其他的執行緒喚醒該執行緒.
notify:執行該方法的執行緒喚醒在等待池中等待的任意一個執行緒,把執行緒轉到鎖池中等待.
notifyAll():執行該方法的執行緒喚醒在等待池中等待的所有的執行緒,把執行緒轉到鎖池中等待.
注意:上述方法只能被同步監聽鎖物件來呼叫,否則報錯IllegalMonitorStateException..
------------------------------------------
假設A執行緒和B執行緒共同操作一個X物件(同步鎖),A,B執行緒可以通過X物件的wait和notify方法來進行通訊,流程如下:
1:當A執行緒執行X物件的同步方法時,A執行緒持有X物件的鎖,B執行緒沒有執行機會,B執行緒在X物件的鎖池中等待.
2:A執行緒在同步方法中執行X.wait()方法時,A執行緒釋放X物件的鎖,進入A執行緒進入X物件的等待池中.
3:在X物件的鎖池中等待鎖的B執行緒獲取X物件的鎖,執行X的另一個同步方法.
4:B執行緒在同步方法中執行X.notify()方法時,JVM把A執行緒從X物件的等待池中移動到X物件的鎖池中,等待獲取鎖.
5:B執行緒執行完同步方法,釋放鎖.A執行緒獲得鎖,繼續執行同步方法.
因此,生產者消費者問題中的共享資源物件類可改為:
//生產者和消費者共同的資源物件
public class ShareResource {
private String name;
private String gender; //一般用列舉來寫性別,這裡先用String
private boolean isEmpty = true;
/**
* 生產者向共享資源儲存資料
* @param name 生產者生產的姓名
* @param gender 生產者生產的性別
*/
synchronized public void push(String name,String gender){
try {
while(!isEmpty){//isEmpty為false時,當前執行緒等待
//if(!isEmpty)用if也可以達到效果,只是while更安全(至少判斷兩次)
this.wait();
}
this.name = name;
Thread.sleep(10);
this.gender = gender;
isEmpty = false;//設定共享資源不為空
this.notify(); //喚醒其他任意一個執行緒,這裡是喚醒一個消費者
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 消費者向共享資源獲取資料
*/
synchronized public void popup(){
try {
while(isEmpty){//當isEmpty為true時,共享資源為空,消費者等待
this.wait();
Thread.sleep(10);
}
System.out.println(this.name + "-" + this.gender);
isEmpty = true;//消費結束,資源應設為空
this.notify();// 喚醒一個生產者
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
除了等待喚醒機制,也可以使用鎖機制
執行緒通訊-使用Lock和Condition介面:
wait和notify方法,只能被同步監聽鎖物件來呼叫,否則報錯IllegalMonitorStateException.
那麼現在問題來了,Lock機制根本就沒有同步鎖了,也就沒有自動獲取鎖和自動釋放鎖的概念.
因為沒有同步鎖,所以Lock機制不能呼叫wait和notify方法.
解決方案:Java5中提供了Lock機制的同時提供了處理Lock機制的通訊控制的Condition介面.
--------------------------------------------------------------------
從Java5開始,可以:
1):使用Lock機制取代synchronized 程式碼塊和synchronized 方法.
2):使用Condition介面物件的await,signal,signalAll方法取代Object類中的wait,notify,notifyAll方法.
共享資源類可改成:
//生產者和消費者共同的資源物件
public class ShareResource {
private String name;
private String gender;
private boolean isEmpty = true;
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
/**
* 生產者向共享資源提供資料
* @param name 生產者生產的名字
* @param gender 性別
*/
public void push(String name,String gender){
lock.lock();
try {
if(!isEmpty){
condition.await();
}
this.name = name;
Thread.sleep(10);
this.gender = gender;
isEmpty = false;
condition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
lock.unlock();
}
}
/**
* 消費者從共享資源獲取(消費)資料
*/
public void popoup(){
lock.lock();
try {
if(isEmpty){
condition.await();
}
Thread.sleep(10);
System.out.println(name+"-"+gender);
isEmpty = true;
condition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
lock.unlock();
}
}
}