线程之间的通信又称为线程同步,是指当某个线程修改了一个对象的值时,另外一个线程能感知到该值的变化并进行相应的操作。

实现线程之间通信的方法有:

1.基于volatile修饰的共享变量

2.通过wait和notify机制

3.Thread.Join方法

4.使用synchronized同步关键字

5.Condition.await/signal方法

Java提供了(wait/notify)等待/通知机制来实现多个线程之间的协同处理,也就是控制线程之间的等待和唤醒。

wait()方法,使当前线程进入阻塞状态,并且释放持有的锁。

notify()方法,唤醒处于阻塞状态的下一个线程

notifyAll()方法,唤醒所有处于阻塞状态下的线程。

线程的通信(wait/notify)等待/通知机制,是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用了对象O的notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续操作。上述两个线程通过对象O来完成交互,而对象上的wait()和notify/notifyAll()的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。

1 生产者/消费者问题

生产者/消费者是经典的线程通信与协同机制,接下来,我们设计一个简单的模型。

生产者:

public class Product implements Runnable {private Queue<String> msg;private int maxSize;public Product(Queue<String> msg, int maxSize) {this.msg = msg;this.maxSize = maxSize;}@Overridepublic void run() {int i=0;while (true) {synchronized (msg) {while (msg.size() == maxSize) {//生产者满了try {msg.wait();} catch (InterruptedException e) {e.printStackTrace();}}try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("生产...");msg.add("生产消息"+i);msg.notify();msg.notifyAll();}}}
}

Producer表示一个生产者线程,该线程定义了一个共享对象msg,然后在run()方法中实现如下逻辑:

  • 使用synchronized(msg)对共享对象加锁。

  • 当msg.size==maxSize时,表示队列满了,让当前线程等待。

  • 否则往bags中添加数据,并且使用msg.notify()方法唤醒阻塞的消费者线程。

从代码中可以发现,wait()和notify()方法必须要写在synchronized代码块中,至于原因 ,后面再分析。

消费者:

public class Consumer implements Runnable {private Queue<String>msg;private int maxSize;public Consumer(Queue<String> msg, int maxSize) {this.msg = msg;this.maxSize = maxSize;}@Overridepublic void run() {while (true){synchronized (msg){//消费者空了while (msg.isEmpty()){try {msg.wait();//阻塞当前队列} catch (InterruptedException e) {e.printStackTrace();}}try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("消费者消费消息"+msg.remove());msg.notify();//唤醒处于等待状态的生产者}}}
}

Consumer表示一个消费者线程,同样会用到共享对象msg,接着在run()方法中实现如下逻辑:

  • 先使用synchronized(msg)方法加锁,注意生产者/消费者锁定的对象实例必须是同一个。

  • 如果msg.isEmpty()表示队列空了,则需要等待生产者生产才能继续消费。

  • 否则,使用msg.remove()方法从队列中消费数据,当数据消费了之后,可以使用msg.notify()方法唤醒处于阻塞状态的生产者线程。

写个测试代码,main方法:

public class TestMain {public static void main(String[] args) {Queue<String>queue=new LinkedList<>();int maxSize=2;Product product=new Product(queue,maxSize);Consumer consumer=new Consumer(queue,maxSize);Thread t1=new Thread(product);Thread t2=new Thread(consumer);t1.start();t2.start();}
}

生产者先生产数据,然后才能唤醒消费者线程,在生产了两个数据之后发现队列满了,此时通过wait()方法阻塞生产者线程。消费者线程开始运行,如果发现队列中的元素不为空,则进行数据的消费,同时唤醒生产者线程,如果消费者发现队列为空 ,则阻塞消费者线程。

生产者/消费者如果深入设计,可以非常复杂,例如,假如有多个消费者或者生产者该如何设计等等。

2 图解生产者/消费者

上面的生产者/消费者模型的执行过程是怎么样的呢?

我们这里用queue做为两者的共享队列,生产者和消费者都会对该共享队列进行读写操作。因此,为了保证原子性,生产者和消费者线程就必须要对该共享队列加锁,只有竞争到锁资源的线程才能加锁,也就是操作队列的数据。

假设生产者抢到了锁,则开始向队列添加数据,知道队列满了发生阻塞,也就是消费应该要消费数据了。在代码中生产者的wait()方法是在synchronized中调用的,此时锁是没有释放的,那么消费者获取不到同步锁该怎么办呢?其实在线程调用了wait()方法后,就会释放当前的同步锁。由于Consumer此时在synchronized同步队列中等待,所以Produer一旦释放锁就可以唤醒Consumer线程。Consumer被唤醒后需要去竞争锁资源,如果成功,则进行数据消费。

Consumer消费一个数据后会调用notify()方法,该方法只是唤醒处于阻塞状态下的线程,由于Consumer还没有释放锁,因此被唤醒的Producer需要等待Consumer释放锁之后才能继续沿着阻塞的位置开始执行。

过程图如下:

16.线程通信1:生产者/消费者问题相关推荐

  1. 线程通信之生产者消费者阻塞队列版

    线程通信之生产者消费者阻塞队列版 ProdConsumer_BlockQueueDemo.java import java.util.concurrent.ArrayBlockingQueue; im ...

  2. Java多线程之线程通信之生产者消费者阻塞队列版

    Java多线程之线程通信之生产者消费者传统版和阻塞队列版 目录 线程通信之生产者消费者传统版 线程通信之生产者消费者阻塞队列版 1. 线程通信之生产者消费者传统版 题目: 一个初始值为零的变量,两个线 ...

  3. 多线程-线程通信:生产者消费者例题

    /*** 线程通信的应用:经典例题:生产者/消费者问题** 生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,* 店员一次只能持有固定数量的产品( ...

  4. 生产者消费者_【线程通信】生产者消费者模型

    1生产者消费者模型介绍 生产者消费者模型,是每一个学习多线程的的人都需要知道的模型; 大致情况就是:有两个线程,一个负责生产产品,一个消费产品,两者公用同一块内存区域,也就是产品放在了同一块内存上面, ...

  5. 线程通信:生产者消费者问题

    1.应用场景: 假设仓库中只能放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中的产品取走. 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,知道仓库中的产品被消费者取走为止. ...

  6. 线程通信,生产者消费者问题案例,模拟来电提醒和接听电话

    package com.cfqp;public class Phone {private static boolean flag = false;public static void main(Str ...

  7. Qt之线程同步(生产者消费者模式 - QSemaphore)

     简述 生产者将数据写入缓冲区,直到它到达缓冲区的末尾,此时,它将从开始位置重新启动,覆盖现有数据.消费者线程读取数据并将其写入标准错误. Semaphore(信号量) 比 mutex(互斥量)有 ...

  8. Qt之线程同步(生产者消费者模式 - QWaitCondition)

     简述 生产者将数据写入缓冲区,直到它到达缓冲区的末尾,这时,它从开始位置重新启动,覆盖现有数据.消费者线程读取数据并将其写入标准错误. Wait condition(等待条件)比单独使用 mut ...

  9. 线程通信问题--生产者和消费者问题

    一.问题引入:首先实现一个线程通信的实例,使用两个线程交替打印输出100以内的数字. 代码实现如下: 1 package com.baozi.exer; 2 3 public class Commun ...

最新文章

  1. HDU 3938 Portal
  2. 网页搜索怎么显示排名_深圳seo搜索排名优化效果怎么样
  3. windows下配置caffe-matlab接口
  4. document.body、document.documentElement和window获取视窗大小的区别
  5. svm gui安装 matlab,svm_matlab_gui 支持向量机matlab工具箱(含资料及gui模式)用于分类和回归预测 - 下载 - 搜珍网...
  6. Eclipse之Android开发环境搭建
  7. 等线PCB布局的13条基本规则
  8. 良心安利动物 恐龙unity3d模型素材网站
  9. 使用Mockito创建Mcok和Spy
  10. Hold住通话有三种方式
  11. iphone java模拟器_【Mac + Appium + Java1.8学习(三)】之IOS自动化环境安装配置以及简单测试用例编写(模拟器、真机)...
  12. 复旦大学与国网上海共建“电力大数据实验室”
  13. 重新编译CDH版本hadoop报错:Non-resolvable parent POM: Could not transfer artifact com.
  14. 大家小心了,做寄生虫排名骗子
  15. 漏损分析指标定义(部分)
  16. krait和kryo_java原生序列化和Kryo序列化性能比较
  17. 20162330 第六周 蓝墨云班课 队列课下作业
  18. vue中实现拖拽调整顺序功能
  19. 制作数据可视化大屏时,可以不再使用千遍一律的主题风格
  20. Mysql数据库,巧妙使用IFNULL()函数,累计求和.

热门文章

  1. 指令寄存器和程序计数器的区别
  2. 独立开发电子商务网站---开发管理
  3. WebGL学习笔记(4)
  4. A Systematic Evaluation of Transient Execution Attacks and Defenses (对暂态执行的攻击和防御的系统评估)(前四节)
  5. 径向基神经网络(RBF)《MATLAB R2015b 神经网络技术》
  6. 【Python】关于使用Python保存图片报错 no attribute ‘to_filename‘ 的解决办法
  7. 2022-2028全球与中国问答平台市场现状及未来发展趋势
  8. Python读取ply文件,并转为sensor_msgs.msg::PointCloud/PointCloud2并发布
  9. Spring freemarker word模板导出List数据
  10. position的属性