目录

简介

一对一生产和消费:一只母鸡和叫练

wait/notify

Lock条件队列

多对多生产和消费:2只母鸡和叫练/叫练媳妇

wait/notifyAll

Lock条件队列

Lock和synchronized比较

总结


简介


多线程通信一直是高频面试考点,有些面试官可能要求现场手写生产者/消费者代码来考察多线程的功底,今天我们以实际生活中母鸡下蛋案例用代码剖析下实现过程。母鸡在鸡窝下蛋了,叫练从鸡窝里把鸡蛋拿出来这个过程,母鸡在鸡窝下蛋,是生产者,叫练捡出鸡蛋,叫练是消费者,一进一出就是线程中的生产者和消费者模型了,鸡窝是放鸡蛋容器。现实中还有很多这样的案例,如医院叫号。下面我们画个图表示下。

一对一生产和消费:一只母鸡和叫练


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代码做了一些修改,创建了两个母鸡线程“小黑”,“小黄”,两个捡鸡蛋的线程“叫练”,“叫练媳妇”,执行结果是同步的,实现了多对多的生产和消费,如下图所示。有如下几点需要注意的地方:

  1. 鸡窝中能容纳最大的鸡蛋是10个。
  2. 下蛋proEggs()方法中判断鸡蛋数量是否大于等于10个使用的是while循环,wait收到通知,唤醒当前线程,需要重新判断一次,避免程序出现逻辑问题,这里不能用if,如果用if,程序可能出现EggsList有超过10以上鸡蛋的情况。这是这道程序中容易出现错误的地方,也是经常会被问到的点,值得重点探究下。
  3. 多对多的生产者和消费者。

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都能让多线程同步。主要异同点表现如下!

  1. 锁性质:Lock乐观锁是非阻塞的,底层是依赖cas+volatile实现,synchronized悲观锁是阻塞的,需要上下文切换。实现思想不一样。
  2. 功能细节上:Lock需要手动加解锁,synchronized自动加解锁。Lock还提供颗粒度更细的功能,比如tryLock等。
  3. 线程通信:Lock提供Condition条件队列,一把锁可以对应多个条件队列,对线程控制更细腻。synchronized只能对应一个wait/notify。

主要就这些吧,如果对synchronized,volatile,cas关键字不太了解的童鞋,可以看看我之前的文章,有很详细的案例和说明。

总结


今天用生活中的例子转化成代码,实现了两种多线程中消费者/生产者模式,给您的建议就是需要把代码敲一遍,如果认真执行了一遍代码应该能看明白,喜欢的请点赞加关注哦。我是叫练【公众号】,边叫边练。

母鸡下蛋实例:多线程通信生产者和消费者wait/notify和condition/await/signal条件队列相关推荐

  1. 多线程通信—生产者和消费者模式

    1.队列Queue: 从一个线程向另一个线程发送数据最安全的方式可能就是使用queue库中的队列了.创建一个被多个线程共享的Queue对象,这些线程通过使用put()和get()操作来向队列中添加或者 ...

  2. Java多线程技术~生产者和消费者问题

    Java多线程技术~生产者和消费者问题 本文是上一篇文章的后续,详情点击该连接 线程通信 应用场景:生产者和消费者问题 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取 ...

  3. java多线程:线程间的通信-生产者和消费者(三)

    在一个程序中,往往会通过多个线程协同来共同完成一项任务,线程间必然需要进行信息的传递,也即是进程间的通信,我们用生产者和消费者的例子来具体分析: 对于生产者和消费者之间的关系,他们都是针对同一资源的操 ...

  4. Java并发编程系列18:多线程之生产者和消费者模式_信号灯法(wait/notify通知机制)

    1.生产者消费者模式 生产者消费者问题(Producer-consumer problem),也称为有限缓冲问题(Bounded-buffer problem),是一个多线程同步问题的经典案例.该问题 ...

  5. java多线程 生产者消费者_java多线程之-生产者与消费者

    java多线程之-并发协作[生产者与消费者]模型 对于多线程程序来说,不管c/c++ java python 等任何编程语言,生产者与消费者模型都是最为经典的.也就是可以说多线程的并发协作 对于此模型 ...

  6. java的知识点34——线程通信 || 生产者与消费者问题

    线程通信 应用场景:生产者和消费者问题 • 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费 • 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待, ...

  7. java多线程生产者与消费者问题_java多线程实现生产者与消费者问题

    生产者与消费者多线程实现,首先的问题就是同步,就是关于临界资源的访问 我们首先来定义一个临界资源类,这里设为Q class Q { int z=4; } 这个int型的z就是我假设的临界资源的个数 然 ...

  8. python 异步 生产者 消费者_python 线程通信 生产者与消费者

    1 """ 2 线程通信的生产者与消费者3 python的queue模块中提供了同步的线程安全的队列类,都具有原子性,实现线程间的同步4 Queue (FIFO: fis ...

  9. java多线程之生产者和消费者问题

    线程通信:不同的线程执行不同的任务,如果这些任务有某种关系,线程之间必须能够通信,协调完成工作. 经典的生产者和消费者案例(Producer/Consumer): 分析案例: 1):生产者和消费者应该 ...

最新文章

  1. 数据库配置下拉框没有数据库可选
  2. CentOS下挂载硬盘(fdisk,mkfs.ext4,mount)
  3. php 设置时区_为什么没有 Asia/Beijing 时区?
  4. Chrome神器Vimium快捷键学习记录
  5. boost::mp11::mp_bind_back相关用法的测试程序
  6. SAP 电商云 Spartacus UI 产品明细页面路由路径的自定义配置
  7. 【开源项目】QT OPENGL 与 shader 绘制展示视频代码实例 OPenGL直接显示YUV数据
  8. 随机过程:指数分布、泊松过程、更新过程(renewal process)+大数定律
  9. 95-140-132-源码-transform-算子Join
  10. python怎么转到下一行_请教:怎样用python读取文件之后,处理在下一行、空行、和*星星?...
  11. Nvidia-Docker
  12. 医院门诊管理系统php文献,医院门诊管理系统(源码+系统)
  13. 工业路由器和家用路由器有什么区别?
  14. 第五次上课 PPT作业——随机数组,并求和
  15. AI如何在医疗中大显身手?
  16. 并网逆变器PI控制(并网模式)
  17. JAVA j2se面试题
  18. 国际贸易相关术语-DDP
  19. spring定时任务需要在项目启动时执行一次
  20. 戴维宁(也叫戴维南)定理是什么?如何证明?_戴维宁定理等效电路求解_戴维宁定理习题

热门文章

  1. HLS播放:html5下用video标签来播放m3u8格式的视频
  2. springboot之ActiveMQ连接池
  3. 自定义浏览器滚动条样式(兼容chrome和firefox)
  4. android壁纸框架,Android仿百度壁纸客户端之搭建主框架(一)
  5. nodejs---express中模板引擎consolidate及路由route简单使用总结
  6. 软件的力量,数字经济时代“海一样的力量”
  7. BFL / PAL status for customer HANA projects
  8. 2020-03-24
  9. 秃头人数越来越多,程序员要怎么自救?摆脱秃头看这个数据分析工具
  10. 大数据24小时:美图发布区块链技术白皮书,百度副总裁邬学斌宣布离职或将加入宝能汽车