母雞下蛋例項:多執行緒通訊生產者和消費者wait/notify和condition/await/signal條件佇列
目錄
簡介
多執行緒通訊一直是高頻面試考點,有些面試官可能要求現場手寫生產者/消費者程式碼來考察多執行緒的功底,今天我們以實際生活中母雞下蛋案例用程式碼剖析下實現過程。母雞在雞窩下蛋了,叫練從雞窩裡把雞蛋拿出來這個過程,母雞在雞窩下蛋,是生產者,叫練撿出雞蛋,叫練是消費者,一進一出就是執行緒中的生產者和消費者模型了,雞窩是放雞蛋容器。現實中還有很多這樣的案例,如醫院叫號。下面我們畫個圖表示下。
一對一生產和消費:一隻母雞和叫練
wait/notify
package com.duyang.thread.basic.waitLock.demo;
import java.util.ArrayList;
import java.util.List;
/**
* @author :jiaolian
* @date :Created in 2020-12-30 16:18
* @description:母雞下蛋:一對一生產者和消費者
* @modified By:
* 公眾號:叫練
*/
public class SingleNotifyWait {
//裝雞蛋的容器
private static class EggsList {
private static final List<String> LIST = new ArrayList();
}
//生產者:母雞實體類
private static class HEN {
private String name;
public HEN(String name) {
this.name = name;
}
//下蛋
public void proEggs() throws InterruptedException {
synchronized (EggsList.class) {
if (EggsList.LIST.size() == 1) {
EggsList.class.wait();
}
//容器新增一個蛋
EggsList.LIST.add("1");
//雞下蛋需要休息才能繼續產蛋
Thread.sleep(1000);
System.out.println(name+":下了一個雞蛋!");
//通知叫練撿蛋
EggsList.class.notify();
}
}
}
//人物件
private static class Person {
private String name;
public Person(String name) {
this.name = name;
}
//取蛋
public void getEggs() throws InterruptedException {
synchronized (EggsList.class) {
if (EggsList.LIST.size() == 0) {
EggsList.class.wait();
}
Thread.sleep(500);
EggsList.LIST.remove(0);
System.out.println(name+":從容器中撿出一個雞蛋");
//通知叫練撿蛋
EggsList.class.notify();
}
}
}
public static void main(String[] args) {
//創造一個人和一隻雞
HEN hen = new HEN("小黑");
Person person = new Person("叫練");
//建立執行緒執行下蛋和撿蛋的過程;
new Thread(()->{
try {
for (int i=0; i<Integer.MAX_VALUE;i++) {
hen.proEggs();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
//叫練撿雞蛋的過程!
new Thread(()->{
try {
for (int i=0; i<Integer.MAX_VALUE;i++) {
person.getEggs();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
如上面程式碼,我們定義EggsList類來裝雞蛋,HEN類表示母雞,Person類表示人。在主函式中建立母雞物件“小黑”,人物件“叫練”, 建立兩個執行緒分別執行下蛋和撿蛋的過程。程式碼中定義雞窩中最多隻能裝一個雞蛋(當然可以定義多個)。詳細過程:“小黑”母雞執行緒和“叫練”執行緒執行緒競爭鎖,如果“小黑”母雞執行緒先獲取鎖,發現EggsList雞蛋的個數大於0,表示有雞蛋,那就呼叫wait等待並釋放鎖給“叫練”執行緒,如果沒有雞蛋,就呼叫EggsList.LIST.add("1")表示生產了一個雞蛋並通知“叫練”來取雞蛋並釋放鎖讓“叫練”執行緒獲取鎖。“叫練”執行緒呼叫getEggs()方法獲取鎖後發現,如果雞窩中並沒有雞蛋就呼叫wait等待並釋放鎖通知“小黑”執行緒獲取鎖去下蛋,如果有雞蛋,說明“小黑”已經下蛋了,就把雞蛋取走,因為雞窩沒有雞蛋了,所以最後也要通知呼叫notify()方法通知“小黑”去下蛋,我們觀察程式的執行結果如下圖。兩個執行緒是死迴圈程式會一直執行下去,下蛋和撿蛋的過程中用到的鎖的是EggsList類的class,“小黑”和“叫練”競爭的都是統一把鎖,所以這個是同步的。這就是母雞“小黑”和“叫練”溝通的過程。
神馬???雞和人能溝通!!
Lock條件佇列
package com.duyang.thread.basic.waitLock.demo;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author :jiaolian
* @date :Created in 2020-12-30 16:18
* @description:母雞下蛋:一對一生產者和消費者 條件佇列
* @modified By:
* 公眾號:叫練
*/
public class SingleCondition {
private static Lock lock = new ReentrantLock();
//條件佇列
private static Condition condition = lock.newCondition();
//裝雞蛋的容器
private static class EggsList {
private static final List<String> LIST = new ArrayList();
}
//生產者:母雞實體類
private static class HEN {
private String name;
public HEN(String name) {
this.name = name;
}
//下蛋
public void proEggs() {
try {
lock.lock();
if (EggsList.LIST.size() == 1) {
condition.await();
}
//容器新增一個蛋
EggsList.LIST.add("1");
//雞下蛋需要休息才能繼續產蛋
Thread.sleep(1000);
System.out.println(name+":下了一個雞蛋!");
//通知叫練撿蛋
condition.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
//人物件
private static class Person {
private String name;
public Person(String name) {
this.name = name;
}
//取蛋
public void getEggs() {
try {
lock.lock();
if (EggsList.LIST.size() == 0) {
condition.await();
}
Thread.sleep(500);
EggsList.LIST.remove(0);
System.out.println(name+":從容器中撿出一個雞蛋");
//通知叫練撿蛋
condition.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
//創造一個人和一隻雞
HEN hen = new HEN("小黑");
Person person = new Person("叫練");
//建立執行緒執行下蛋和撿蛋的過程;
new Thread(()->{
for (int i=0; i<Integer.MAX_VALUE;i++) {
hen.proEggs();
}
}).start();
//叫練撿雞蛋的過程!
new Thread(()->{
for (int i=0; i<Integer.MAX_VALUE;i++) {
person.getEggs();
}
}).start();
}
}
如上面程式碼,只是將synchronized換成了Lock,程式執行的結果和上面的一致,wait/notify換成了AQS的條件佇列Condition來控制執行緒之間的通訊。Lock需要手動加鎖lock.lock(),解鎖lock.unlock()的步驟放在finally程式碼塊保證鎖始終能被釋放。await底層是unsafe.park(false,0)呼叫C++程式碼實現。
多對多生產和消費:2只母雞和叫練/叫練媳婦
wait/notifyAll
package com.duyang.thread.basic.waitLock.demo;
import java.util.ArrayList;
import java.util.List;
/**
* @author :jiaolian
* @date :Created in 2020-12-30 16:18
* @description:母雞下蛋:多對多生產者和消費者
* @modified By:
* 公眾號:叫練
*/
public class MultNotifyWait {
//裝雞蛋的容器
private static class EggsList {
private static final List<String> LIST = new ArrayList();
}
//生產者:母雞實體類
private static class HEN {
private String name;
public HEN(String name) {
this.name = name;
}
//下蛋
public void proEggs() throws InterruptedException {
synchronized (EggsList.class) {
while (EggsList.LIST.size() >= 10) {
EggsList.class.wait();
}
//容器新增一個蛋
EggsList.LIST.add("1");
//雞下蛋需要休息才能繼續產蛋
Thread.sleep(1000);
System.out.println(name+":下了一個雞蛋!共有"+EggsList.LIST.size()+"個蛋");
//通知叫練撿蛋
EggsList.class.notify();
}
}
}
//人物件
private static class Person {
private String name;
public Person(String name) {
this.name = name;
}
//取蛋
public void getEggs() throws InterruptedException {
synchronized (EggsList.class) {
while (EggsList.LIST.size() == 0) {
EggsList.class.wait();
}
Thread.sleep(500);
EggsList.LIST.remove(0);
System.out.println(name+":從容器中撿出一個雞蛋!還剩"+EggsList.LIST.size()+"個蛋");
//通知叫練撿蛋
EggsList.class.notify();
}
}
}
public static void main(String[] args) {
//創造一個人和一隻雞
HEN hen1 = new HEN("小黑");
HEN hen2 = new HEN("小黃");
Person jiaolian = new Person("叫練");
Person wife = new Person("叫練媳婦");
//建立執行緒執行下蛋和撿蛋的過程;
new Thread(()->{
try {
for (int i=0; i<Integer.MAX_VALUE;i++) {
hen1.proEggs();
Thread.sleep(50);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(()->{
try {
for (int i=0; i<Integer.MAX_VALUE;i++) {
hen2.proEggs();
Thread.sleep(50);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
//叫練撿雞蛋的執行緒!
new Thread(()->{
try {
for (int i=0; i<Integer.MAX_VALUE;i++) {
jiaolian.getEggs();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
//叫練媳婦撿雞蛋的執行緒!
new Thread(()->{
try {
for (int i=0; i<Integer.MAX_VALUE;i++) {
wife.getEggs();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
如上面程式碼,參照一對一生產和消費中wait/notify程式碼做了一些修改,建立了兩個母雞執行緒“小黑”,“小黃”,兩個撿雞蛋的執行緒“叫練”,“叫練媳婦”,執行結果是同步的,實現了多對多的生產和消費,如下圖所示。有如下幾點需要注意的地方:
- 雞窩中能容納最大的雞蛋是10個。
- 下蛋proEggs()方法中判斷雞蛋數量是否大於等於10個使用的是while迴圈,wait收到通知,喚醒當前執行緒,需要重新判斷一次,避免程式出現邏輯問題,這裡不能用if,如果用if,程式可能出現EggsList有超過10以上雞蛋的情況。這是這道程式中容易出現錯誤的地方,也是經常會被問到的點,值得重點探究下。
- 多對多的生產者和消費者。
Lock條件佇列
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author :jiaolian
* @date :Created in 2020-12-30 16:18
* @description:母雞下蛋:多對多生產者和消費者 條件佇列
* @modified By:
* 公眾號:叫練
*/
public class MultCondition {
private static Lock lock = new ReentrantLock();
//條件佇列
private static Condition condition = lock.newCondition();
//裝雞蛋的容器
private static class EggsList {
private static final List<String> LIST = new ArrayList();
}
//生產者:母雞實體類
private static class HEN {
private String name;
public HEN(String name) {
this.name = name;
}
//下蛋
public void proEggs() {
try {
lock.lock();
while (EggsList.LIST.size() >= 10) {
condition.await();
}
//容器新增一個蛋
EggsList.LIST.add("1");
//雞下蛋需要休息才能繼續產蛋
Thread.sleep(1000);
System.out.println(name+":下了一個雞蛋!共有"+ EggsList.LIST.size()+"個蛋");
//通知叫練/叫練媳婦撿蛋
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
//人物件
private static class Person {
private String name;
public Person(String name) {
this.name = name;
}
//取蛋
public void getEggs() throws InterruptedException {
try {
lock.lock();
while (EggsList.LIST.size() == 0) {
condition.await();
}
Thread.sleep(500);
EggsList.LIST.remove(0);
System.out.println(name+":從容器中撿出一個雞蛋!還剩"+ EggsList.LIST.size()+"個蛋");
//通知叫練撿蛋
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
//創造一個人和一隻雞
HEN hen1 = new HEN("小黑");
HEN hen2 = new HEN("小黃");
Person jiaolian = new Person("叫練");
Person wife = new Person("叫練媳婦");
//建立執行緒執行下蛋和撿蛋的過程;
new Thread(()->{
try {
for (int i=0; i<Integer.MAX_VALUE;i++) {
hen1.proEggs();
Thread.sleep(50);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(()->{
try {
for (int i=0; i<Integer.MAX_VALUE;i++) {
hen2.proEggs();
Thread.sleep(50);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
//叫練撿雞蛋的執行緒!
new Thread(()->{
try {
for (int i=0; i<Integer.MAX_VALUE;i++) {
jiaolian.getEggs();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
//叫練媳婦撿雞蛋的執行緒!
new Thread(()->{
try {
for (int i=0; i<Integer.MAX_VALUE;i++) {
wife.getEggs();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
如上面程式碼,只是將synchronized換成了Lock,程式執行的結果和上面的一致,下面我們比較下Lock和synchronized的異同。這個問題也是面試中會經常問到的!
Lock和synchronized比較
Lock和synchronized都能讓多執行緒同步。主要異同點表現如下!
- 鎖性質:Lock樂觀鎖是非阻塞的,底層是依賴cas+volatile實現,synchronized悲觀鎖是阻塞的,需要上下文切換。實現思想不一樣。
- 功能細節上:Lock需要手動加解鎖,synchronized自動加解鎖。Lock還提供顆粒度更細的功能,比如tryLock等。
- 執行緒通訊:Lock提供Condition條件佇列,一把鎖可以對應多個條件佇列,對執行緒控制更細膩。synchronized只能對應一個wait/notify。
主要就這些吧,如果對synchronized,volatile,cas關鍵字不太瞭解的童鞋,可以看看我之前的文章,有很詳細的案例和說明。
總結
今天用生活中的例子轉化成程式碼,實現了兩種多執行緒中消費者/生產者模式,給您的建議就是需要把程式碼敲一遍,如果認真執行了一遍程式碼應該能看明白,喜歡的請點贊加關注哦。我是叫練【公眾號】,邊叫邊練。
相關文章
- 使用Python佇列和多執行緒實現生產者消費者Python佇列執行緒
- Java 多執行緒學習(執行緒通訊——消費者和生產者)Java執行緒
- java多執行緒:執行緒間通訊——生產者消費者模型Java執行緒模型
- python多執行緒+生產者和消費者模型+queue使用Python執行緒模型
- 執行緒安全(三個條件)Synchronzied,wait和notify執行緒AI
- 阻塞佇列和生產者-消費者模式佇列模式
- python執行緒通訊與生產者消費者模式Python執行緒模式
- C#多執行緒學習(三) 生產者和消費者C#執行緒
- 使用slice和條件變數實現一個簡單的多生產者多消費者佇列變數佇列
- Java多執行緒——生產者消費者示例Java執行緒
- Java併發包原始碼學習系列:詳解Condition條件佇列、signal和awaitJava原始碼佇列AI
- 條件佇列大法好:wait和notify的基本語義佇列AI
- 2.Python程式間的通訊之佇列(Queue)和生產者消費者模型Python佇列模型
- python中多執行緒消費者生產者問題Python執行緒
- Python-多執行緒及生產者與消費者Python執行緒
- java進階(40)--wait與notify(生產者與消費者模式)JavaAI模式
- Java多執行緒中的wait/notify通訊模式Java執行緒AI模式
- Java 多執行緒基礎(十二)生產者與消費者Java執行緒
- Java多執行緒——消費者與生產者的關係Java執行緒
- python 多執行緒實現生產者與消費者模型Python執行緒模型
- Java多執行緒 -- wait() 和 notify() 使用入門Java執行緒AI
- Java 多執行緒(Java.Thread)------ 執行緒協作(生產者消費者模式)Java執行緒thread模式
- 【Java】【多執行緒】兩個執行緒間的通訊、wait、notify、notifyAllJava執行緒AI
- 多執行緒併發如何高效實現生產者/消費者?執行緒
- Python並行程式設計(六):多執行緒同步之queue(佇列)實現生產者-消費者模型Python並行行程程式設計執行緒佇列模型
- Java多執行緒中wait 和 notify 方法理解Java執行緒AI
- 生產者消費者模式--java多執行緒同步方法的應用模式Java執行緒
- Thinking in Java---執行緒通訊+三種方式實現生產者消費者問題ThinkingJava執行緒
- PHP操作Beanstalkd佇列(2)生產者與消費者PHPBean佇列
- ActiveMQ 生產者和消費者demoMQ
- 多執行緒(一)、基礎概念及notify()和wait()的使用執行緒AI
- 十五、.net core(.NET 6)搭建RabbitMQ訊息佇列生產者和消費者的簡單方法MQ佇列
- 執行緒同步介紹及 生產者消費者問題舉例 C#版執行緒C#
- 多執行緒,執行緒類三種方式,執行緒排程,執行緒同步,死鎖,執行緒間的通訊,阻塞佇列,wait和sleep區別?執行緒佇列AI
- 執行緒間的同步與通訊(2)——wait, notify, notifyAll執行緒AI
- 執行緒間的協作(2)——生產者與消費者模式執行緒模式
- java多執行緒 wait() notify()簡單使用Java執行緒AI
- Java實現生產者和消費者Java