0

0

java多线程之并发协作生产者消费者设计模式

伊谢尔伦

伊谢尔伦

发布时间:2016-11-21 13:31:37

|

1979人浏览过

|

来源于php中文网

原创

 两个线程一个生产者个一个消费者

  需求情景

  两个线程,一个负责生产,一个负责消费,生产者生产一个,消费者消费一个

  涉及问题

  同步问题:如何保证同一资源被多个线程并发访问时的完整性。常用的同步方法是采用标记或加锁机制

  wait() / nofity() 方法是基类object的两个方法,也就意味着所有java类都会拥有这两个方法,这样,我们就可以为任何对象实现同步机制。

  wait()方法:当缓冲区已满/空时,生产者/消费者线程停止自己的执行,放弃锁,使自己处于等等状态,让其他线程执行。

  notify()方法:当生产者/消费者向缓冲区放入/取出一个产品时,向其他等待的线程发出可执行的通知,同时放弃锁,使自己处于等待状态。

  代码实现(共三个类和一个main方法的测试类)

  Resource.java
  /**
  * Created by yuandl on 2016-10-11./**
  * 资源
  */
  public class Resource {
  /*资源序号*/
  private int number = 0;
  /*资源标记*/
  private boolean flag = false;
  /**
  * 生产资源
  */
  public synchronized void create() {
  if (flag) {//先判断标记是否已经生产了,如果已经生产,等待消费;
  try {
  wait();//让生产线程等待
  } catch (InterruptedException e) {
  e.printStackTrace();
  }
  }
  number++;//生产一个
  System.out.println(Thread.currentThread().getName() + "生产者------------" + number);
  flag = true;//将资源标记为已经生产
  notify();//唤醒在等待操作资源的线程(队列)
  }
  /**
  * 消费资源
  */
  public synchronized void destroy() {
  if (!flag) {
  try {
  wait();
  } catch (InterruptedException e) {
  e.printStackTrace();
  }
  }
  System.out.println(Thread.currentThread().getName() + "消费者****" + number);
  flag = false;
  notify();
  }
 }

  Producer.java 

  /**
  * Created by yuandl on 2016-10-11.
  *
  /**
  * 生产者
  */
  public class Producer implements Runnable {
  private Resource resource;
  public Producer(Resource resource) {
  this.resource = resource;
  }
  @Override
  public void run() {
  while (true) {
  try {
  Thread.sleep(10);
  } catch (InterruptedException e) {
  e.printStackTrace();
  }
  resource.create();
  }
  }
  }

 Consumer.java

 /**
  * 消费者
  */
  public class Consumer implements Runnable {
  private Resource resource;
  public Consumer(Resource resource) {
  this.resource = resource;
  }
  @Override
  public void run() {
  while (true) {
  try {
  Thread.sleep(10);
  } catch (InterruptedException e) {
  e.printStackTrace();
  }
  resource.destroy();
  }
  }
  }

 ProducerConsumerTest.java

  /**
  * Created by yuandl on 2016-10-11.
  */
  public class ProducerConsumerTest {
  public static void main(String args[]) {
  Resource resource = new Resource();
  new Thread(new Producer(resource)).start();//生产者线程
  new Thread(new Consumer(resource)).start();//消费者线程
  }
  }

打印结果

立即学习Java免费学习笔记(深入)”;

Thread-0生产者------------1
 Thread-1消费者****1
  Thread-0生产者------------2
  Thread-1消费者****2
  Thread-0生产者------------3
  Thread-1消费者****3
  Thread-0生产者------------4
  Thread-1消费者****4
  Thread-0生产者------------5
  Thread-1消费者****5
  Thread-0生产者------------6
  Thread-1消费者****6
  Thread-0生产者------------7
  Thread-1消费者****7
  Thread-0生产者------------8
  Thread-1消费者****8
  Thread-0生产者------------9
  Thread-1消费者****9
  Thread-0生产者------------10
  Thread-1消费者****10

以上打印结果可以看出没有任何问题


  多个线程,多个生产者和多个消费者的问题

  需求情景

  四个线程,两个个负责生产,两个个负责消费,生产者生产一个,消费者消费一个

  涉及问题

  notifyAll()方法:当生产者/消费者向缓冲区放入/取出一个产品时,向其他等待的所有线程发出可执行的通知,同时放弃锁,使自己处于等待状态。

  再次测试代码

 

 ProducerConsumerTest.java
  /**
  * Created by yuandl on 2016-10-11.
  */
  public class ProducerConsumerTest {
  public static void main(String args[]) {
  Resource resource = new Resource();
  new Thread(new Consumer(resource)).start();//生产者线程
  new Thread(new Consumer(resource)).start();//生产者线程
  new Thread(new Producer(resource)).start();//消费者线程
  new Thread(new Producer(resource)).start();//消费者线程
  }
  }

运行结果

Magician
Magician

Figma插件,AI生成图标、图片和UX文案

下载
  Thread-0生产者------------100
  Thread-3消费者****100
  Thread-0生产者------------101
  Thread-3消费者****101
  Thread-2消费者****101
  Thread-1生产者------------102
  Thread-3消费者****102
  Thread-0生产者------------103
  Thread-2消费者****103
  Thread-1生产者------------104
  Thread-3消费者****104
  Thread-1生产者------------105
  Thread-0生产者------------106
  Thread-2消费者****106
  Thread-1生产者------------107
  Thread-3消费者****107
  Thread-0生产者------------108
  Thread-2消费者****108
  Thread-0生产者------------109
  Thread-2消费者****109
  Thread-1生产者------------110
  Thread-3消费者****110

 通过以上打印结果发现问题


  101生产了一次,消费了两次

  105生产了,而没有消费

  原因分析

  当两个线程同时操作生产者生产或者消费者消费时,如果有生产者或者的两个线程都wait()时,再次notify(),由于其中一个线程已经改变了标记而另外一个线程再次往下直接执行的时候没有判断标记而导致的。

  if判断标记,只有一次,会导致不该运行的线程运行了。出现了数据错误的情况。

  解决方案

  while判断标记,解决了线程获取执行权后,是否要运行!也就是每次wait()后再notify()时先再次判断标记

  代码改进(Resource中的if->while)

  Resource.java

 
 /**
  * Created by yuandl on 2016-10-11./**
  * 资源
  */
  public class Resource {
  /*资源序号*/
  private int number = 0;
  /*资源标记*/
  private boolean flag = false;
  /**
  * 生产资源
  */
  public synchronized void create() {
  while (flag) {//先判断标记是否已经生产了,如果已经生产,等待消费;
  try {
  wait();//让生产线程等待
  } catch (InterruptedException e) {
  e.printStackTrace();
  }
  }
  number++;//生产一个
  System.out.println(Thread.currentThread().getName() + "生产者------------" + number);
  flag = true;//将资源标记为已经生产
  notify();//唤醒在等待操作资源的线程(队列)
  }
  /**
  * 消费资源
  */
  public synchronized void destroy() {
  while (!flag) {
  try {
  wait();
  } catch (InterruptedException e) {
  e.printStackTrace();
  }
  }
  System.out.println(Thread.currentThread().getName() + "消费者****" + number);
  flag = false;
  notify();
  }
  }

再次发现问题


  打印到某个值比如生产完74,程序运行卡死了,好像锁死了一样。

  原因分析

  notify:只能唤醒一个线程,如果本方唤醒了本方,没有意义。而且while判断标记+notify会导致”死锁”。

  解决方案

  notifyAll解决了本方线程一定会唤醒对方线程的问题。

  最后代码改进(Resource中的notify()->notifyAll())

  Resource.java 

 /**
  * Created by yuandl on 2016-10-11./**
  * 资源
  */
  public class Resource {
  /*资源序号*/
  private int number = 0;
  /*资源标记*/
  private boolean flag = false;
  /**
  * 生产资源
  */
  public synchronized void create() {
  while (flag) {//先判断标记是否已经生产了,如果已经生产,等待消费;
  try {
  wait();//让生产线程等待
  } catch (InterruptedException e) {
  e.printStackTrace();
  }
  }
  number++;//生产一个
  System.out.println(Thread.currentThread().getName() + "生产者------------" + number);
  flag = true;//将资源标记为已经生产
  notifyAll();//唤醒在等待操作资源的线程(队列)
  }
  /**
  * 消费资源
  */
  public synchronized void destroy() {
  while (!flag) {
  try {
  wait();
  } catch (InterruptedException e) {
  e.printStackTrace();
  }
  }
  System.out.println(Thread.currentThread().getName() + "消费者****" + number);
  flag = false;
  notifyAll();
  }
  }

 运行结果

Thread-0生产者------------412
  Thread-2消费者****412
  Thread-0生产者------------413
  Thread-3消费者****413
  Thread-1生产者------------414
  Thread-2消费者****414
  Thread-1生产者------------415
  Thread-2消费者****415
  Thread-0生产者------------416
  Thread-3消费者****416
  Thread-1生产者------------417
  Thread-3消费者****417
  Thread-0生产者------------418
  Thread-2消费者****418
  Thread-0生产者------------419
  Thread-3消费者****419
  Thread-1生产者------------420
  Thread-2消费者****420

以上就大功告成了,没有任何问题

相关文章

java速学教程(入门到精通)
java速学教程(入门到精通)

java怎么学习?java怎么入门?java在哪学?java怎么学才快?不用担心,这里为大家提供了java速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
Java 桌面应用开发(JavaFX 实战)
Java 桌面应用开发(JavaFX 实战)

本专题系统讲解 Java 在桌面应用开发领域的实战应用,重点围绕 JavaFX 框架,涵盖界面布局、控件使用、事件处理、FXML、样式美化(CSS)、多线程与UI响应优化,以及桌面应用的打包与发布。通过完整示例项目,帮助学习者掌握 使用 Java 构建现代化、跨平台桌面应用程序的核心能力。

37

2026.01.14

php与html混编教程大全
php与html混编教程大全

本专题整合了php和html混编相关教程,阅读专题下面的文章了解更多详细内容。

19

2026.01.13

PHP 高性能
PHP 高性能

本专题整合了PHP高性能相关教程大全,阅读专题下面的文章了解更多详细内容。

37

2026.01.13

MySQL数据库报错常见问题及解决方法大全
MySQL数据库报错常见问题及解决方法大全

本专题整合了MySQL数据库报错常见问题及解决方法,阅读专题下面的文章了解更多详细内容。

19

2026.01.13

PHP 文件上传
PHP 文件上传

本专题整合了PHP实现文件上传相关教程,阅读专题下面的文章了解更多详细内容。

16

2026.01.13

PHP缓存策略教程大全
PHP缓存策略教程大全

本专题整合了PHP缓存相关教程,阅读专题下面的文章了解更多详细内容。

6

2026.01.13

jQuery 正则表达式相关教程
jQuery 正则表达式相关教程

本专题整合了jQuery正则表达式相关教程大全,阅读专题下面的文章了解更多详细内容。

3

2026.01.13

交互式图表和动态图表教程汇总
交互式图表和动态图表教程汇总

本专题整合了交互式图表和动态图表的相关内容,阅读专题下面的文章了解更多详细内容。

45

2026.01.13

nginx配置文件详细教程
nginx配置文件详细教程

本专题整合了nginx配置文件相关教程详细汇总,阅读专题下面的文章了解更多详细内容。

9

2026.01.13

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 2.5万人学习

C# 教程
C# 教程

共94课时 | 6.7万人学习

Java 教程
Java 教程

共578课时 | 45.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号