溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Java等待、喚醒機(jī)制的原理以及生產(chǎn)者消費(fèi)者模式的詳解介紹

發(fā)布時(shí)間:2021-09-06 17:38:48 來源:億速云 閱讀:136 作者:chen 欄目:編程語言

這篇文章主要介紹“Java等待、喚醒機(jī)制的原理以及生產(chǎn)者消費(fèi)者模式的詳解介紹”,在日常操作中,相信很多人在Java等待、喚醒機(jī)制的原理以及生產(chǎn)者消費(fèi)者模式的詳解介紹問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對(duì)大家解答”Java等待、喚醒機(jī)制的原理以及生產(chǎn)者消費(fèi)者模式的詳解介紹”的疑惑有所幫助!接下來,請(qǐng)跟著小編一起來學(xué)習(xí)吧!

生產(chǎn)者消費(fèi)者模式是多線程中最為常見的模式:生產(chǎn)者線程(一個(gè)或多個(gè))生成面包放進(jìn)籃子里(集合或數(shù)組),同時(shí),消費(fèi)者線程(一個(gè)或多個(gè))從籃子里(集合或數(shù)組)取出面包消耗。雖然它們?nèi)蝿?wù)不同,但處理的資源是相同的,這體現(xiàn)的是一種線程間通信方式。

本文將先說明單生產(chǎn)者單消費(fèi)者的情況,之后再說明多生產(chǎn)者多消費(fèi)者模式的情況。還會(huì)分別使用wait()/nofity()/nofityAll()機(jī)制、lock()/unlock()機(jī)制實(shí)現(xiàn)這兩種模式。

在開始介紹模式之前,先解釋下wait()、notify()和notifyAll()方法的用法細(xì)節(jié)以及改進(jìn)的lock()/unlock()、await()/signal()/signalAll()的用法。

1.等待、喚醒機(jī)制的原理

wait()、notify()和notifyAll()分別表示讓線程進(jìn)入睡眠、喚醒睡眠線程以及喚醒所有睡眠的線程。但是,對(duì)象是哪個(gè)線程呢?另外,在API文檔中描述這三個(gè)方法都必須在有效監(jiān)視器(可理解為持有鎖)的前提下使用。這三個(gè)方法和鎖有什么關(guān)系呢?

以同步代碼塊synchronized(obj){}或同步函數(shù)為例,在它們的代碼結(jié)構(gòu)中可以使用wait()、notify()以及notifyAll(),因?yàn)樗鼈兌汲钟墟i。

對(duì)于下面的兩個(gè)同步代碼塊來說,分別使用的是鎖obj1和鎖obj2,其中線程1、線程2執(zhí)行的是obj1對(duì)應(yīng)的同步代碼,線程3、線程4執(zhí)行的是obj2對(duì)應(yīng)的同步代碼。

class MyLock implements Runnable {
 public int flag = 0;
 Object obj1 = new Object();
 Object obj2 = new Object();
 public void run(){
 while(true){
  if(flag%2=0){
  synchronized(obj1){ //線程t1和t2執(zhí)行此同步任務(wù)
   //try{obj1.wait();}catch(InterruptedException i){}
   //obj1.notify()
   //obj1.notifyAll()
  }
  } else {
  synchronized(obj2){ //線程t3和t4執(zhí)行此同步任務(wù)
   //try{obj2.wait();}catch(InterruptedException i){}
   //obj2.notify()
   //obj2.notifyAll()
  }
  }
 }
 }
}
class Demo {
 public static void main(String[] args){
 MyLock ml = new MyLock();
 Thread t1 = new Thread(ml);
 Thread t2 = new Thread(ml);
 Thread t3 = new Thread(ml);
 Thread t4 = new Thread(ml);
 t1.start();
 t2.start();
 try{Thread.sleep(1)}catch(InterruptedException i){};
 ml.flag++;
 t3.start();
 t4.start();
 }
}

當(dāng)t1開始執(zhí)行到wait()時(shí),它將進(jìn)入睡眠狀態(tài),但卻不是一般的睡眠,而是在一個(gè)被obj1標(biāo)識(shí)的線程池中睡眠(實(shí)際上是監(jiān)視器對(duì)應(yīng)線程池,只不過此時(shí)的監(jiān)視器和鎖是綁定在一起的)。當(dāng)t2開始執(zhí)行,它發(fā)現(xiàn)鎖obj1被其他線程持有,它將進(jìn)入睡眠態(tài),這次睡眠是因?yàn)殒i資源等待而非wait()進(jìn)入的睡眠。因?yàn)閠2已經(jīng)判斷過它要申請(qǐng)的是obj1鎖,因此它也會(huì)進(jìn)入obj1這個(gè)線程池睡眠,而不是普通的睡眠。同理t3和t4,這兩個(gè)線程會(huì)進(jìn)入obj2線程池睡眠。

當(dāng)某個(gè)線程執(zhí)行到notify()時(shí),這個(gè)notify()將 隨機(jī) 喚醒它 所屬鎖對(duì)應(yīng)線程池 中的 任意一個(gè) 線程。例如,obj1.notify()將喚醒obj1線程池中任意一個(gè)睡眠的線程(當(dāng)然,如果沒有睡眠線程則什么也不做)。同理notifyAll()則是喚醒所屬鎖對(duì)應(yīng)線程池中所有睡眠的線程。

必須要搞清楚的是"對(duì)應(yīng)鎖",因?yàn)樵谡{(diào)用wait()、notify()和notifyAll()時(shí)都必須明確指定鎖。例如,obj1.wait()。如果省略了所屬鎖,則表示的是this這個(gè)對(duì)象,也就是說,只有在非靜態(tài)的同步函數(shù)中才能省略這三個(gè)方法的前綴。

簡而言之,當(dāng)使用了同步,就使用了鎖,線程也就有了歸屬,它的所有依據(jù)都由所屬鎖來決定。例如,線程同步時(shí),判斷鎖是否空閑以決定是否執(zhí)行后面的代碼,亦決定是否去特定的線程池中睡眠,當(dāng)喚醒時(shí)也只會(huì)喚醒所屬鎖對(duì)應(yīng)線程池中的線程。

這幾個(gè)方法在應(yīng)用上,一般在一次任務(wù)中,wait()和notify()/notifyAll()是成對(duì)出現(xiàn)且擇一執(zhí)行的。換句話說,就是這一輪原子性同步執(zhí)行過程中,要么執(zhí)行wait()進(jìn)入睡眠,要么執(zhí)行notify()喚醒線程池中的睡眠線程。要如何實(shí)現(xiàn)擇一執(zhí)行,可以考慮使用標(biāo)記的方式來作為判斷依據(jù)。參考后文的例子。

2.Lock和Condition

wait()系列的三個(gè)方法局限性很大,因?yàn)闊o論是睡眠還是喚醒的動(dòng)作,都完全和鎖耦合在一起了。例如,鎖obj1關(guān)聯(lián)的線程只能喚醒obj1線程池中的線程,而無法喚醒鎖obj2關(guān)聯(lián)的線程;再例如,在原來synchronized同步時(shí),鎖是在開始同步時(shí)隱式地自動(dòng)獲取的,且是在執(zhí)行完一整個(gè)任務(wù)后,又隱式地自動(dòng)釋放鎖,也就是說獲取鎖和釋放鎖的動(dòng)作無法人為控制。

從JDK 1.5開始,java提供了java.util.concurrent.locks包,這個(gè)包中提供了Lock接口、Condition接口和ReadWriteLock接口,前兩個(gè)接口將鎖和監(jiān)視器方法(睡眠、喚醒操作)解耦了。其中Lock接口只提供鎖,通過鎖方法newConditon()可以生成一個(gè)或多個(gè)與該鎖關(guān)聯(lián)的監(jiān)視器,每個(gè)監(jiān)視器都有自己的睡眠、喚醒方法。也就是說Lock替代了synchronized方法和同步代碼塊的使用,Condition替代了Object監(jiān)視器方法的使用。

如下圖:

Java等待、喚醒機(jī)制的原理以及生產(chǎn)者消費(fèi)者模式的詳解介紹

當(dāng)某線程執(zhí)行condition1.await()時(shí),該線程將進(jìn)入condition1監(jiān)視器對(duì)應(yīng)的線程池睡眠,當(dāng)執(zhí)行condition1.signal()時(shí),將隨機(jī)喚醒condition1線程池中的任意一個(gè)線程,當(dāng)執(zhí)行condition1.signalAll()時(shí),將喚醒condition1線程池中的所有線程。同理,對(duì)于condition2監(jiān)視器也是一樣的。

即使有多個(gè)監(jiān)視器,但只要它們關(guān)聯(lián)的是同一個(gè)鎖對(duì)象,就可以跨監(jiān)視器操作對(duì)方線程。例如condition1中的線程可以執(zhí)行condition2.signal()來喚醒condition2線程池中的某個(gè)線程。

要使用這種鎖、監(jiān)視器的關(guān)聯(lián)方式,參考如下步驟:

import java.util.concurrent.locks.*;
Lock l = new ReentrantLock();
Condition con1 = l.newCondition();
condition con2 = l.newCondition();
l.lock();
try{
 //包含await()、signal()或signalAll()的代碼段...
} finally {
 l.unlock(); //由于代碼段可能異常,但unlock()是必須執(zhí)行的,所以必須使用try,且將unlock()放進(jìn)finally段
}

具體用法見后文關(guān)于Lock、condition的示例代碼。

3.單生產(chǎn)者單消費(fèi)者模式

一個(gè)生產(chǎn)者線程,一個(gè)消費(fèi)者線程,生產(chǎn)者每生產(chǎn)一個(gè)面包放進(jìn)盤子里,消費(fèi)者從盤子里取出面包進(jìn)行消費(fèi)。其中生產(chǎn)者判斷是否繼續(xù)生產(chǎn)的依據(jù)是盤子里沒有面包,而消費(fèi)者判斷是否消費(fèi)的依據(jù)是盤子里有面包。由于這個(gè)模式中,盤子一直只放一個(gè)面包,因此可以把盤子省略掉,生產(chǎn)者和消費(fèi)者直接手把手地交遞面包即可。

首先需要描述這三個(gè)類,一是多線程共同操作的資源(此處即面包),二是生產(chǎn)者,三是消費(fèi)者。在下面的例子中,我把生產(chǎn)面包和消費(fèi)面包的方法分別封裝到了生產(chǎn)者和消費(fèi)者類中,如果把它們封裝在面包類中則更容易理解。

//描述資源:面包的名稱和編號(hào),由編號(hào)決定面包的號(hào)碼
class Bread {
 public String name;
 public int count = 1;
 public boolean flag = false; //該標(biāo)記為wait()和notify()提供判斷標(biāo)記
}
//生產(chǎn)者和消費(fèi)者先后處理的面包資源是同一個(gè),要確保這一點(diǎn),
//可以按單例模式來設(shè)計(jì)面包類,也可以將同一個(gè)面包對(duì)象通過構(gòu)造方法傳遞給生產(chǎn)者和消費(fèi)者,此處使用后一種方式。
//描述生產(chǎn)者
class Producer implements Runnable {
 private Bread b; //生產(chǎn)者的成員:它要處理的資源
 Producer(Bread b){
 this.b = b;
 }
 //提供生產(chǎn)面包的方法
 public void produce(String name){
 b.name = name + b.count;
 b.count++;
 }
 public void run(){
 while(true){
  synchronized(Bread.class){ //使用Bread.class作為鎖標(biāo)識(shí),使得生產(chǎn)者和消費(fèi)者的同步代碼塊可以使用同一個(gè)鎖
  if(b.flag){  //wait()必須在同步代碼塊內(nèi)部,不僅因?yàn)楸仨毘钟墟i才能睡眠,而且對(duì)鎖這個(gè)資源的判斷會(huì)出現(xiàn)混亂
   try{Bread.class.wait();}catch(InterruptedException i){}
  }
  produce("面包");
  System.out.println(Thread.currentThread().getName()+"----生產(chǎn)者------"+b.name);
  try{Thread.sleep(10);}catch(InterruptedException i){}
  b.flag = true;  //標(biāo)記的切換也必須在保持同步
  Bread.class.notify(); //notify()也必須同步,否則鎖都已經(jīng)釋放了,就無法做喚醒動(dòng)作
  //ps:一次同步任務(wù)中,wait()和notify()應(yīng)當(dāng)只能其中一個(gè)執(zhí)行,否則對(duì)方線程會(huì)混亂
  }
 }
 }
}
//描述消費(fèi)者
class Consumer implements Runnable {
 private Bread b; //消費(fèi)者的成員:它要處理的資源
 Consumer(Bread b){
 this.b = b;
 }
 //提供消費(fèi)面包的方法
 public String consume(){
 return b.name;
 }
 public void run(){
 while(true){
  synchronized(Bread.class){
  if(!b.flag){
   try{Bread.class.wait();}catch(InterruptedException i){}
  }
  System.out.println(Thread.currentThread().getName()+"----消費(fèi)者-------------"+consume());
  try{Thread.sleep(10);}catch(InterruptedException i){}
  b.flag = false;
  Bread.class.notify();
  }
 }
 }
}
public class ProduceConsume_1{
 public static void main(String[] args) {
 //1.創(chuàng)建資源對(duì)象
 Bread b = new Bread();
 //2.創(chuàng)建生產(chǎn)者和消費(fèi)者對(duì)象,將同一個(gè)面包對(duì)象傳遞給生產(chǎn)者和消費(fèi)者
 Producer pro = new Producer(b);
 Consumer con = new Consumer(b);
 //3.創(chuàng)建線程對(duì)象
 Thread pro_t = new Thread(pro);
 Thread con_t = new Thread(con);
 pro_t.start();
 con_t.start();
 }
}

最后的執(zhí)行結(jié)果應(yīng)當(dāng)生產(chǎn)一個(gè)、消費(fèi)一個(gè),如此不斷循環(huán)。如下:

Thread-0----生產(chǎn)者------面包1
Thread-1----消費(fèi)者-------------面包1
Thread-0----生產(chǎn)者------面包2
Thread-1----消費(fèi)者-------------面包2
Thread-0----生產(chǎn)者------面包3
Thread-1----消費(fèi)者-------------面包3
Thread-0----生產(chǎn)者------面包4
Thread-1----消費(fèi)者-------------面包4
Thread-0----生產(chǎn)者------面包5
Thread-1----消費(fèi)者-------------面包5
Thread-0----生產(chǎn)者------面包6
Thread-1----消費(fèi)者-------------面包6

4.使用Lock和Condition實(shí)現(xiàn)單生產(chǎn)單消費(fèi)模式

代碼如下:

import java.util.concurrent.locks.*;
class Bread {
 public String name;
 public int count = 1;
 public boolean flag = false;
 //為生產(chǎn)者和消費(fèi)者提供同一個(gè)鎖對(duì)象以及同一個(gè)Condition對(duì)象
 public static Lock lock = new ReentrantLock();
 public static Condition condition = lock.newCondition();
}
class Producer implements Runnable {
 private Bread b;
 Producer(Bread b){
 this.b = b;
 }
 public void produce(String name){
 b.name = name + b.count;
 b.count++;
 }
 public void run(){
 while(true){
  //使用Bread.lock來鎖住資源
  Bread.lock.lock(); 
  try{
  if(b.flag){
   try{Bread.condition.await();}catch(InterruptedException i){}
  }
  produce("面包");
  System.out.println(Thread.currentThread().getName()+"----生產(chǎn)者------"+b.name);
  try{Thread.sleep(10);}catch(InterruptedException i){}
  b.flag = true; 
  Bread.condition.signal(); 
  } finally {
  Bread.lock.unlock();
  }
 }
 }
}
class Consumer implements Runnable {
 private Bread b; 
 Consumer(Bread b){
 this.b = b;
 }
 public String consume(){
 return b.name;
 }
 public void run(){
 while(true){
  //使用Bread.lock來鎖住資源
  Bread.lock.lock();
  try{
  if(!b.flag){
   try{Bread.condition.await();}catch(InterruptedException i){}
  }
  System.out.println(Thread.currentThread().getName()+"----消費(fèi)者-------------"+consume());
  try{Thread.sleep(10);}catch(InterruptedException i){}
  b.flag = false;
  Bread.condition.signal();
  } finally {
  Bread.lock.unlock();
  }
 }
 }
}
public class ProduceConsume_1{
 public static void main(String[] args) {
 //1.創(chuàng)建資源對(duì)象
 Bread b = new Bread();
 //2.創(chuàng)建生產(chǎn)者和消費(fèi)者對(duì)象,將同一個(gè)面包對(duì)象傳遞給生產(chǎn)者和消費(fèi)者
 Producer pro = new Producer(b);
 Consumer con = new Consumer(b);
 //3.創(chuàng)建線程對(duì)象
 Thread pro_t = new Thread(pro);
 Thread con_t = new Thread(con);
 pro_t.start();
 con_t.start();
 }
}

5.多生產(chǎn)多消費(fèi)模式(單面包)

這里先說明多生產(chǎn)者多消費(fèi)者,但同一個(gè)時(shí)刻最多只能有一個(gè)面包的模式,這個(gè)模式在實(shí)際中可能是不理想的,但為了引出后面真實(shí)的多生產(chǎn)多消費(fèi)模式,我覺得有必要在這里解釋這種模式,并且分析這種模式以及如何從單生產(chǎn)單消費(fèi)的代碼演變而來。

如下圖:

Java等待、喚醒機(jī)制的原理以及生產(chǎn)者消費(fèi)者模式的詳解介紹

從單生產(chǎn)單消費(fèi)到多生產(chǎn)多消費(fèi),因?yàn)槎嗑€程安全問題和死鎖問題,所以有兩個(gè)方面的問題需要考慮:

對(duì)于某一方來說,如何讓多線程達(dá)到和單線程同樣的生產(chǎn)或消費(fèi)能力?也就是說,如何讓多線程看上去就是單線程。多線程和單線程最大的區(qū)別在于多線程安全問題,因此,只要保證多線程執(zhí)行的任務(wù)能夠同步即可。

第1個(gè)問題考慮的是某一方多線程的問題,第2個(gè)問題考慮的是兩方如何能和諧配合完成生產(chǎn)消費(fèi)問題。也就是如何保證生產(chǎn)方和消費(fèi)方一方活動(dòng)的同時(shí)另一方睡眠。只需在某一方執(zhí)行完同步任務(wù)時(shí),喚醒另一方即可。

其實(shí)從單線程到多線程,就兩個(gè)問題需要考慮:不同步和死鎖。(1)當(dāng)生產(chǎn)方和消費(fèi)方都出現(xiàn)了多線程,可以將生產(chǎn)方的多線程看成一個(gè)線程整體、消費(fèi)方的多線程也看成一個(gè)整體,這解決的是線程安全問題。(2)再將生產(chǎn)方整體和消費(fèi)方整體兩方結(jié)合起來看成多線程,來解決死鎖問題,而java中解決死鎖的方式就是喚醒對(duì)方或喚醒所有。

問題是如何保證某一方的多線程之間同步?以多線程執(zhí)行單消費(fèi)方的代碼為例進(jìn)行分析。

while(true){
 synchronized(Bread.class){
 if(!b.flag){
  try{Bread.class.wait();}catch(InterruptedException i){}
 }
 System.out.println(Thread.currentThread().getName()+"----消費(fèi)者-------------"+consume());
 try{Thread.sleep(10);}catch(InterruptedException i){}
 b.flag = false;
 Bread.class.notify();
 }
}

假設(shè)消費(fèi)線程1消費(fèi)完一個(gè)面包后喚醒了消費(fèi)線程2,并繼續(xù)循環(huán),判斷if(!flag),它將wait,于是鎖被釋放。假設(shè)CPU正好選中了消費(fèi)線程2,那么消費(fèi)線程2也將進(jìn)入wait。當(dāng)生產(chǎn)方生產(chǎn)了一個(gè)面包后,假設(shè)喚醒了消費(fèi)線程1,它將從wait語句處繼續(xù)向下消費(fèi)剛生產(chǎn)完的面包,假設(shè)正好再次喚醒了消費(fèi)線程2,當(dāng)消費(fèi)線程2被CPU選中后,消費(fèi)線程2也將從wait語句處向下消費(fèi),消費(fèi)的也是剛才生產(chǎn)的面包,問題再此出現(xiàn)了,連續(xù)喚醒的消費(fèi)線程1和2消費(fèi)的是同一個(gè)面包,也就是說面包被重復(fù)消費(fèi)了。這又是多線程不同步問題。

說了一大段,其實(shí)將視線放大后分析就很簡單了,只要某一方的2個(gè)或多個(gè)線程都因?yàn)榕袛郻.flag而wait,那么這兩個(gè)或多個(gè)線程有可能會(huì)被連續(xù)喚醒而繼續(xù)向下生產(chǎn)或消費(fèi)。這造成了多線程不同步問題。

不安全的問題就出在同一方的多個(gè)線程在連續(xù)喚醒后繼續(xù)向下生產(chǎn)或消費(fèi)。這是if語句引起的,如果能夠讓wait的線程在喚醒后還回頭判斷b.flag是否為true,就能讓其決定是否繼續(xù)wait還是向下生產(chǎn)或消費(fèi)。

可以將if語句替換為while語句來滿足要求。這樣一來,無論某一方的多個(gè)線程是否被連續(xù)喚醒,它們都將回頭判斷b.flag。

while(true){
 synchronized(Bread.class){
 while(!b.flag){
  try{Bread.class.wait();}catch(InterruptedException i){}
 }
 System.out.println(Thread.currentThread().getName()+"----消費(fèi)者-------------"+consume());
 try{Thread.sleep(10);}catch(InterruptedException i){}
 b.flag = false;
 Bread.class.notify();
 }
}

解決了第一個(gè)多線程安全的問題,但會(huì)出現(xiàn)死鎖問題。這很容易分析,將生產(chǎn)方看作一個(gè)整體,將消費(fèi)方也看作一個(gè)整體,當(dāng)生產(chǎn)方線程都wait了(生產(chǎn)方的線程被連續(xù)喚醒時(shí)會(huì)出現(xiàn)該方線程全部wait),消費(fèi)方也都wait了,死鎖就出現(xiàn)了。其實(shí)放大了看,將生產(chǎn)方、消費(fèi)方分別看作一個(gè)線程,這兩個(gè)線程組成多線程,當(dāng)某一方wait后無法喚醒另一方,另一方也一定會(huì)wait,于是就死鎖了。

對(duì)于雙方死鎖的問題,只要保證能喚醒對(duì)方,而非本方連續(xù)喚醒就能解決。使用notifyAll()或signalAll()即可,也可以通過signal()喚醒對(duì)方線程解決,見下面的第二段代碼。

根據(jù)上面的分析,將單生產(chǎn)、單消費(fèi)模式的代碼改進(jìn)一下,就可以變?yōu)槎嗌a(chǎn)多消費(fèi)單面包模式。

//代碼段1
class Bread {
 public String name;
 public int count = 1;
 public boolean flag = false; 
}
//描述生產(chǎn)者
class Producer implements Runnable {
 private Bread b; 
 Producer(Bread b){
  this.b = b;
 }
 public void produce(String name){
  b.name = name + b.count;
  b.count++;
 }
 public void run(){
  while(true){
    synchronized(Bread.class){
     while(b.flag){ 
      try{Bread.class.wait();}catch(InterruptedException i){}
     }
     produce("面包");
     System.out.println(Thread.currentThread().getName()+"----生產(chǎn)者------"+b.name);
     try{Thread.sleep(10);}catch(InterruptedException i){}
     b.flag = true; 
     Bread.class.notifyAll();
   }
  }
 }
}
//描述消費(fèi)者
class Consumer implements Runnable {
 private Bread b;
 Consumer(Bread b){
  this.b = b;
 }
 public String consume(){
  return b.name;
 }
 public void run(){
  while(true){
    synchronized(Bread.class){
     while(!b.flag){
      try{Bread.class.wait();}catch(InterruptedException i){}
     }
     System.out.println(Thread.currentThread().getName()+"----消費(fèi)者-------------"+consume());
     try{Thread.sleep(10);}catch(InterruptedException i){}
     b.flag = false;
     Bread.class.notifyAll();
    }
  }
 }
}
public class ProduceConsume_5 {
 public static void main(String[] args) {
  //1.創(chuàng)建資源對(duì)象
  Bread b = new Bread();
  //2.創(chuàng)建生產(chǎn)者和消費(fèi)者對(duì)象
  Producer pro = new Producer(b);
  Consumer con = new Consumer(b);
  //3.創(chuàng)建線程對(duì)象
  Thread pro_t1 = new Thread(pro); //生產(chǎn)線程1
  Thread pro_t2 = new Thread(pro); //生產(chǎn)線程2
  Thread con_t1 = new Thread(con); //消費(fèi)線程1
  Thread con_t2 = new Thread(con); //消費(fèi)線程2
  pro_t1.start();
  pro_t2.start();
  con_t1.start();
  con_t2.start();
 }
}

以下是采用Lock和Conditon重構(gòu)后的代碼,使用的是signal()喚醒對(duì)方線程的方法。

//代碼段2
import java.util.concurrent.locks.*;
class Bread {
 public String name;
 public int count = 1;
 public boolean flag = false;
 public static Lock lock = new ReentrantLock();
 public static Condition pro_con = lock.newCondition();
 public static Condition con_con = lock.newCondition();
}
//描述生產(chǎn)者
class Producer implements Runnable {
 private Bread b;
 Producer(Bread b){
  this.b = b;
 }
 public void produce(String name){
  b.name = name + b.count;
  b.count++;
 }
 public void run(){
  while(true){
   Bread.lock.lock();
   try{
    while(b.flag){
     try{Bread.pro_con.await();}catch(InterruptedException i){}
    }
    produce("面包");
    System.out.println(Thread.currentThread().getName()+"----生產(chǎn)者------"+b.name);
    try{Thread.sleep(10);}catch(InterruptedException i){}
    b.flag = true;
    Bread.con_con.signal(); //喚醒的是consumer線程
   } finally {
    Bread.lock.unlock();
   }
  }
 }
}
//描述消費(fèi)者
class Consumer implements Runnable {
 private Bread b;
 Consumer(Bread b){
  this.b = b;
 }
 public String consume(){
  return b.name;
 }
 public void run(){
  while(true){
   Bread.lock.lock();
   try{
    while(!b.flag){
     try{Bread.con_con.await();}catch(InterruptedException i){}
    }
    System.out.println(Thread.currentThread().getName()+"----消費(fèi)者-------------"+consume());
    try{Thread.sleep(10);}catch(InterruptedException i){}
    b.flag = false;
    Bread.pro_con.signal();  //喚醒的是producer線程
   } finally {
    Bread.lock.unlock();
   }
  }
 }
}
public class ProduceConsume_6 {
 public static void main(String[] args) {
  //1.創(chuàng)建資源對(duì)象
  Bread b = new Bread();
  //2.創(chuàng)建生產(chǎn)者和消費(fèi)者對(duì)象
  Producer pro = new Producer(b);
  Consumer con = new Consumer(b);
  //3.創(chuàng)建線程對(duì)象
  Thread pro_t1 = new Thread(pro);
  Thread pro_t2 = new Thread(pro);
  Thread con_t1 = new Thread(con);
  Thread con_t2 = new Thread(con);
  pro_t1.start();
  pro_t2.start();
  con_t1.start();
  con_t2.start();
 }
}

關(guān)于多生產(chǎn)、多消費(fèi)問題做個(gè)總結(jié):

(1).解決某一方多線程不同步的方案是使用while(flag)來判斷是否wait;

(2).解決雙方死鎖問題的方案是喚醒對(duì)方,可以使用notifyAll(),signalAll()或?qū)Ψ奖O(jiān)視器的signal()方法。

6.多生產(chǎn)多消費(fèi)模式

有多個(gè)生產(chǎn)者線程,多個(gè)消費(fèi)者線程,生產(chǎn)者將生產(chǎn)的面包放進(jìn)籃子(集合或數(shù)組)里,消費(fèi)者從籃子里取出面包。生產(chǎn)者判斷繼續(xù)生產(chǎn)的依據(jù)是籃子已經(jīng)滿了,消費(fèi)者判斷繼續(xù)消費(fèi)的依據(jù)是籃子是否空了。此外,當(dāng)消費(fèi)者取出面包后,對(duì)應(yīng)的位置又空了,生產(chǎn)者可以回頭從籃子的起始位置繼續(xù)生產(chǎn),這可以通過重置籃子的指針來實(shí)現(xiàn)。

在這個(gè)模式里,除了描述生產(chǎn)者、消費(fèi)者、面包,還需要描述籃子這個(gè)容器。假設(shè)使用數(shù)組作為容器,生產(chǎn)者每生產(chǎn)一個(gè),生產(chǎn)指針向后移位,消費(fèi)者每消費(fèi)一個(gè),消費(fèi)指針向后移位。

代碼如下:可參考API-->Condition類中給出的示例代碼

import java.util.concurrent.locks.*;
class Basket {
 private Bread[] arr;
 //the size of basket
 Basket(int size){
  arr = new Bread[size];
 }
 //the pointer of in and out
 private int in_ptr,out_ptr;
 //how many breads left in basket
 private int left;
 private Lock lock = new ReentrantLock();
 private Condition full = lock.newCondition();
 private Condition empty = lock.newCondition();
 //bread into basket
 public void in(){
  lock.lock();
  try{
   while(left == arr.length){
    try{full.await();} catch (InterruptedException i) {i.printStackTrace();}
   }
   arr[in_ptr] = new Bread("MianBao",Producer.num++);
   System.out.println("Put the bread: "+arr[in_ptr].getName()+"------into basket["+in_ptr+"]");
   left++;
   if(++in_ptr == arr.length){in_ptr = 0;}
   empty.signal();
  } finally {
   lock.unlock();
  }
 }
 //bread out from basket
 public Bread out(){
  lock.lock();
  try{
   while(left == 0){
    try{empty.await();} catch (InterruptedException i) {i.printStackTrace();}
   }
   Bread out_bread = arr[out_ptr];
   System.out.println("Get the bread: "+out_bread.getName()+"-----------from basket["+out_ptr+"]");
   left--;
   if(++out_ptr == arr.length){out_ptr = 0;}
   full.signal();
   return out_bread;
  } finally {
   lock.unlock();
  }
 }
}
class Bread {
 private String name;
 Bread(String name,int num){
  this.name = name + num;
 }
 public String getName(){
  return this.name;
 }
}
class Producer implements Runnable {
 private Basket basket;
 public static int num = 1; //the first number for Bread's name
 Producer(Basket b){
  this.basket = b;
 }
 public void run(){
  while(true) {
   basket.in();
   try{Thread.sleep(10);}catch(InterruptedException i){}
  }
 }
}
class Consumer implements Runnable {
 private Basket basket;
 private Bread i_get;
 Consumer(Basket b){
  this.basket = b;
 }
 public void run(){
  while(true){
   i_get = basket.out();
   try{Thread.sleep(10);}catch(InterruptedException i){}
  }
 }
}
public class ProduceConsume_7 {
 public static void main(String[] args) {
  Basket b = new Basket(20); // the basket size = 20
  Producer pro = new Producer(b);
  Consumer con = new Consumer(b);
  Thread pro_t1 = new Thread(pro);
  Thread pro_t2 = new Thread(pro);
  Thread con_t1 = new Thread(con);
  Thread con_t2 = new Thread(con);
  Thread con_t3 = new Thread(con);
  pro_t1.start();
  pro_t2.start();
  con_t1.start();
  con_t2.start();
  con_t3.start();
 }
}

這里涉及了消費(fèi)者、生產(chǎn)者、面包和籃子,其中面包和籃子是多線程共同操作的資源,生產(chǎn)者線程生產(chǎn)面包放進(jìn)籃子,消費(fèi)者線程從籃子中取出面包。理想的代碼是將生產(chǎn)任務(wù)和消費(fèi)任務(wù)都封裝在資源類中,因?yàn)槊姘腔@子容器的元素,所以不適合封裝到面包類中,而且封裝到籃子中,能更方便地操作容器。

注意,一定要將所有涉及資源操作的代碼都放進(jìn)鎖的內(nèi)部,否則會(huì)產(chǎn)生多線程不同步問題。例如,在Producer類中定義了生產(chǎn)面包的方法produce(),然后將其作為放進(jìn)籃子的方法basket.in()的參數(shù),即basket.in(producer()),這是錯(cuò)誤的行為,因?yàn)閜roduce()是在鎖的外部執(zhí)行后才傳遞給in()方法的。

到此,關(guān)于“Java等待、喚醒機(jī)制的原理以及生產(chǎn)者消費(fèi)者模式的詳解介紹”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI