大家好,上篇文章为大家介绍了线程间通信和协作的一些基本方式,那这篇文章就来介绍一下经典的wait-notify机制吧。

什么是wait-notify机制?

想象一下有两个线程A、B,如果业务场景中需要这两个线程交替执行任务(比如A执行完一次任务后换B执行,B执行完后再换A执行这样重复交替),之前的基本通信方式只能让线程暂停一段指定时间,Join方法也无法做到这种交替执行的要求,那怎么办呢?

别急,针对这种场景java同样为我们提供了一种经典的线程通信方式——wait-notify机制,这里涉及到下面的三个方法(关于锁的知识后文会详细讲):

wait方法:当前线程在调用wait方法会先让出当前线程持有的对象锁以便让其他线程能够获取,然后当前线程会停止执行并进入WAITING状态。直到接收到唤醒或中断信号后,当前线程才会继续尝试获取对象锁。如果此时获取对象锁成功,就能继续执行任务。

notify方法:当前线程的任务即将执行完毕并发出唤醒信号,此时只有接收到唤醒信号的线程才会尝试获取对象锁。当然此时可能获取对象锁会失败,因为notify方法不会即时释放锁,而是需要等到线程执行完毕后才会真正释放锁。

notifyAll方法:和notify方法作用相似,唯一不同的就是该方法会对当前所有在等待这个对象锁的线程发出唤醒信号。至于最终是哪个线程抢到了对象锁,就要看哪个线程比较“幸运”啦。

关于这几个方法,还有以下两点需要关注:

1.wait、notify、notifyAll这三个方法都是Object类中定义的,而Object类是所有类的父类,所以在java中的所有对象都会继承这三个方法。

2.这三个方法必须在同步块中被调用(之后会介绍同步块),如果在同步块之外调用这三个方法,java会抛出java.lang.IllegalMonitorStateException这个异常。

基于wait-notify机制的单生产者-单消费者模型

上面已经介绍了wait-notify机制用到的方法以及需要注意的点,实际上针对这个机制,有一个非常著名、非常经典的模型——生产者消费者模型。

什么是生产者-消费者模型呢?简单来说就是这么个场景:有两种线程分别是生产者线程和消费者线程,还有一个固定大小的资源队列。

生产者的任务是根据原料生产出产品,并将生产好的产品往队列里扔;消费者的任务呢就是从队列里面拿已经生产好的产品去进行包装。

我们可以看到在这个场景中,因为队列可容纳的资源是有限的,所以当队列满时,生产者就没办法继续往队列里放产品,此时生产者就需要等待消费者从队列里拿走产品后,才能继续往队列里放产品;

而消费者也是一样,当队列为空时,消费者就无法从队列里拿到产品,此时就需要等待生产者成功生产出产品并往队列里扔,才能继续从队列里拿产品。

这个场景是wait-notify机制最适合发挥作用的场景,下面是一个单生产者-单消费者的模拟代码:

  1 /**
  2  * 基于wait-notify机制的单生产者-消费者模型
  3  */
  4 public class ProducerAndConsumer {
  5
  6     public static void main(String[] args) {
  7         Resource resource = new Resource();
  8         //生产者线程
  9         ProducerThread p1 = new ProducerThread(resource);
 10         //消费者线程
 11         ConsumerThread c1 = new ConsumerThread(resource);
 12
 13         p1.start();
 14         c1.start();
 15
 16     }
 17 }
 18
 19
 20 /**
 21  * 公共资源类
 22  * @author
 23  *
 24  */
 25 class Resource{//重要
 26     //当前资源数量
 27     private int num = 0;
 28     //资源池中允许存放的资源数目
 29     private int size = 10;
 30
 31     /**
 32      * 从资源池中取走资源
 33      */
 34     public synchronized void remove(){
 35         if(num > 0){
 36             num--;
 37             System.out.println("消费者" + Thread.currentThread().getName() +
 38                     "消耗一件资源," + "当前线程池有" + num + "个");
 39             notifyAll();//通知生产者生产资源
 40         }else{
 41             try {
 42                 //如果没有资源,则消费者进入等待状态
 43                 wait();
 44                 System.out.println("消费者" + Thread.currentThread().getName() + "线程进入等待状态");
 45             } catch (InterruptedException e) {
 46                 e.printStackTrace();
 47             }
 48         }
 49     }
 50     /**
 51      * 向资源池中添加资源
 52      */
 53     public synchronized void add(){
 54         if(num < size){
 55             num++;
 56             System.out.println("生产者" + Thread.currentThread().getName() + "生产一件资源,当前资源池有"
 57                     + num + "个");
 58             //通知等待的消费者
 59             notifyAll();
 60         }else{
 61             //如果当前资源池中有10件资源
 62             try{
 63                 wait();//生产者进入等待状态,并释放锁
 64                 System.out.println(Thread.currentThread().getName()+"线程进入等待");
 65             }catch(InterruptedException e){
 66                 e.printStackTrace();
 67             }
 68         }
 69     }
 70 }
 71
 72
 73 /**
 74  * 消费者线程
 75  */
 76 class ConsumerThread extends Thread{
 77     private Resource resource;
 78     public ConsumerThread(Resource resource){
 79         this.resource = resource;
 80     }
 81     @Override
 82     public void run() {
 83         while(true){
 84             try {
 85                 Thread.sleep(1000);
 86             } catch (InterruptedException e) {
 87                 e.printStackTrace();
 88             }
 89             resource.remove();
 90         }
 91     }
 92 }
 93
 94
 95 /**
 96  * 生产者线程
 97  */
 98 class ProducerThread extends Thread{
 99     private Resource resource;
100     public ProducerThread(Resource resource){
101         this.resource = resource;
102     }
103     @Override
104     public void run() {
105         //不断地生产资源
106         while(true){
107             try {
108                 Thread.sleep(1000);
109             } catch (InterruptedException e) {
110                 e.printStackTrace();
111             }
112             resource.add();
113         }
114     }
115
116 }

View Code

童鞋们可以运行代码试试,这里资源池最大允许放10个产品。

这里留一个问题给大家思考,如果我这里的add和remove方法不加synchronized修饰,就会抛出java.lang.IllegalMonitorStateException异常,那么是什么原因导致java必须要这么做呢?我会在介绍synchronized关键字的时候公布答案。

好了,wait-notify机制到这里就介绍完毕,希望大家能够理解。下篇文章会为大家讲解一下volatile这个关键字的用法。

转载于:https://www.cnblogs.com/smartchen/p/9280877.html

线程间通信与协作方式之——wait-notify机制相关推荐

  1. 线程间通信的常用方式

    线程间通信的常用方式 1.简介 线程通信简单来说就是实现线程的交替工作,传递信息.例如在一个方法中我有两个线程A和B在运行,我希望线程A先向一个集合里面循环新增数据,当增加到第五次的时候,线程B才开始 ...

  2. 【java笔记】线程间通信(1):等待唤醒机制

    线程间通信概念:多个线程处理同一个资源,但是处理的动作却不相同 必要性:多个线程并发执行时,在默认情况下CPU是随机切换线程的,当需要多个线程来共同完成一件任务,并且希望它们有规律的执行,那么多线程之 ...

  3. android 线程间通信几种方式

    1.共享变量(内存) 2.管道 3.handle机制 runOnUiThread(Runnable) view.post(Runnable)

  4. 线程间通信的几种实现方式

    线程间通信的几种实现方式 首先,要短信线程间通信的模型有两种:共享内存和消息传递,以下方式都是基本这两种模型来实现的.我们来基本一道面试常见的题目来分析: 题目:有两个线程A.B,A线程向一个集合里面 ...

  5. 线程间通信的几种方法_并发编程中的线程间通信

    线程通信的目标是使线程间能够互相发送信号.另一方面,线程通信使线程能够等待其他线程的信号. 线程通信常用的方式有: wait/notify 等待 Volatile 内存共享 CountDownLatc ...

  6. 【Java 并发编程】多线程、线程同步、死锁、线程间通信(生产者消费者模型)、可重入锁、线程池

    并发编程(Concurrent Programming) 进程(Process).线程(Thread).线程的串行 多线程 多线程的原理 多线程的优缺点 Java并发编程 默认线程 开启新线程 `Ru ...

  7. 【java笔记】线程间通信(2):生产者和消费者案例分析

    [java笔记]线程间通信(1):等待唤醒机制_m0_52043808的博客-CSDN博客 类: 资源类:包子类:皮,馅,有无 生产者: 包子铺类(线程类)(继承Thread) 设置线程任务(run) ...

  8. Java线程间通信-回调的实现方式

    2019独角兽企业重金招聘Python工程师标准>>> Java线程间通信-回调的实现方式 Java线程间通信是非常复杂的问题的.线程间通信问题本质上是如何将与线程相关的变量或者对象 ...

  9. linux系统线程通信的几种方式,Linux进程间通信-线程间通信

    Linux作为一种新兴的操作系统,几乎支持所有的Unix下常用的进程间通信方法:管道.消息队列.共享内存.信号量.套接口. 1.管道 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动 ...

  10. android 多线程间通信,android实现线程间通信的四种常见方式

    1,通过Handler机制 主线程中定义Handler,子线程发消息,通知Handler完成UI更新,Handler对象必须定义在主线程中,如果是多个类直接互相调用,就不是很方便,需要传递conten ...

最新文章

  1. 计算机视觉>>PCV安装和使用
  2. Python urllib与requests、XML和HTMLParser
  3. C#实现bitmap图像矫正
  4. spark-sql执行时报错:
  5. Could not load java.net.BindException错误解决
  6. Java经典编程题50道之三十四
  7. 三、处理机调度与死锁
  8. oracle手动删除数据库
  9. iPhone 12顶配版延期到10月:刘海仍在 后置3摄+雷达
  10. BZOJ3309 DZY Loves Math 【莫比乌斯反演】
  11. 【考研数学】函数、极限、连续
  12. AutoCad二次开发
  13. linux 网站图片无法加载失败怎么办,网页无法加载图片怎么办?解决网页图片无法显示的方法...
  14. Homebrew完美卸载软件及其依赖包
  15. 《漫画机器学习入门》总结
  16. Windows文件夹或文件名过长无法删除
  17. bokeh与tornado结合的三种方式
  18. 我们如何一键识别?拍照识别植物的软件有哪些?
  19. 前端编程中利用PS切图还原设计图
  20. 网络带宽和下载速度的换算

热门文章

  1. 使用Trace实现程序日志
  2. python名称空间_一篇文章搞懂Python的类与对象名称空间
  3. golang gin解决跨域:编写一个全局中间件
  4. PHP根据配置的规则,计算用户的等级
  5. Mysql查询某列最长字符串记录
  6. Linux用php上传表单文件,文件太大提示[413 Request Entity Too Large]
  7. 编译OpenJDK8-u332:/bin/sh: 1: [: -a: unexpected operator/line 0: [: too many arguments
  8. E: flAbsPath on /var/lib/dpkg/status failed - realpath (2: 没有那个文件或目录)
  9. 编译imsdroid,折腾了半天,还是弃用了Android Studio,换用Eclipse
  10. 天津西站,大屏幕程序出错啦