UDN-企业互联网技术人气社区

板块导航

浏览  : 1069
回复  : 0

[讨论交流] Java多线程之并发协作生产者消费者设计模式

[复制链接]
呵呵燕的头像 楼主
  两个线程一个生产者个一个消费者

  需求情景


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

  涉及问题

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

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

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

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

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

  Resource.java

  1.   /**

  2.   * Created by yuandl on 2016-10-11./**

  3.   * 资源

  4.   */

  5.   public class Resource {

  6.   /*资源序号*/

  7.   private int number = 0;

  8.   /*资源标记*/

  9.   private boolean flag = false;

  10.   /**

  11.   * 生产资源

  12.   */

  13.   public synchronized void create() {

  14.   if (flag) {//先判断标记是否已经生产了,如果已经生产,等待消费;

  15.   try {

  16.   wait();//让生产线程等待

  17.   } catch (InterruptedException e) {

  18.   e.printStackTrace();

  19.   }

  20.   }

  21.   number++;//生产一个

  22.   System.out.println(Thread.currentThread().getName() + "生产者------------" + number);

  23.   flag = true;//将资源标记为已经生产

  24.   notify();//唤醒在等待操作资源的线程(队列)

  25.   }

  26.   /**

  27.   * 消费资源

  28.   */

  29.   public synchronized void destroy() {

  30.   if (!flag) {

  31.   try {

  32.   wait();

  33.   } catch (InterruptedException e) {

  34.   e.printStackTrace();

  35.   }

  36.   }

  37.   System.out.println(Thread.currentThread().getName() + "消费者****" + number);

  38.   flag = false;

  39.   notify();

  40.   }

  41.  }
复制代码

  Producer.java

 
  1.   /**

  2.   * Created by yuandl on 2016-10-11.

  3.   *

  4.   /**

  5.   * 生产者

  6.   */

  7.   public class Producer implements Runnable {

  8.   private Resource resource;

  9.   public Producer(Resource resource) {

  10.   this.resource = resource;

  11.   }

  12.   @Override

  13.   public void run() {

  14.   while (true) {

  15.   try {

  16.   Thread.sleep(10);

  17.   } catch (InterruptedException e) {

  18.   e.printStackTrace();

  19.   }

  20.   resource.create();

  21.   }

  22.   }

  23.   }
复制代码


  Consumer.java

  1.   /**

  2.   * 消费者

  3.   */

  4.   public class Consumer implements Runnable {

  5.   private Resource resource;

  6.   public Consumer(Resource resource) {

  7.   this.resource = resource;

  8.   }

  9.   @Override

  10.   public void run() {

  11.   while (true) {

  12.   try {

  13.   Thread.sleep(10);

  14.   } catch (InterruptedException e) {

  15.   e.printStackTrace();

  16.   }

  17.   resource.destroy();

  18.   }

  19.   }

  20.   }
复制代码


 
  1.  ProducerConsumerTest.java

  2.   /**

  3.   * Created by yuandl on 2016-10-11.

  4.   */

  5.   public class ProducerConsumerTest {

  6.   public static void main(String args[]) {

  7.   Resource resource = new Resource();

  8.   new Thread(new Producer(resource)).start();//生产者线程

  9.   new Thread(new Consumer(resource)).start();//消费者线程

  10.   }

  11.   }
复制代码


  打印结果

  1.   Thread-0生产者------------1

  2.   Thread-1消费者****1

  3.   Thread-0生产者------------2

  4.   Thread-1消费者****2

  5.   Thread-0生产者------------3

  6.   Thread-1消费者****3

  7.   Thread-0生产者------------4

  8.   Thread-1消费者****4

  9.   Thread-0生产者------------5

  10.   Thread-1消费者****5

  11.   Thread-0生产者------------6

  12.   Thread-1消费者****6

  13.   Thread-0生产者------------7

  14.   Thread-1消费者****7

  15.   Thread-0生产者------------8

  16.   Thread-1消费者****8

  17.   Thread-0生产者------------9

  18.   Thread-1消费者****9

  19.   Thread-0生产者------------10

  20.   Thread-1消费者****10
复制代码


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

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

  需求情景

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

  涉及问题

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

  再次测试代码

  ProducerConsumerTest.java

  1.   /**

  2.   * Created by yuandl on 2016-10-11.

  3.   */

  4.   public class ProducerConsumerTest {

  5.   public static void main(String args[]) {

  6.   Resource resource = new Resource();

  7.   new Thread(new Consumer(resource)).start();//生产者线程

  8.   new Thread(new Consumer(resource)).start();//生产者线程

  9.   new Thread(new Producer(resource)).start();//消费者线程

  10.   new Thread(new Producer(resource)).start();//消费者线程

  11.   }

  12.   }
复制代码


  运行结果

  1.   Thread-0生产者------------100

  2.   Thread-3消费者****100

  3.   Thread-0生产者------------101

  4.   Thread-3消费者****101

  5.   Thread-2消费者****101

  6.   Thread-1生产者------------102

  7.   Thread-3消费者****102

  8.   Thread-0生产者------------103

  9.   Thread-2消费者****103

  10.   Thread-1生产者------------104

  11.   Thread-3消费者****104

  12.   Thread-1生产者------------105

  13.   Thread-0生产者------------106

  14.   Thread-2消费者****106

  15.   Thread-1生产者------------107

  16.   Thread-3消费者****107

  17.   Thread-0生产者------------108

  18.   Thread-2消费者****108

  19.   Thread-0生产者------------109

  20.   Thread-2消费者****109

  21.   Thread-1生产者------------110

  22.   Thread-3消费者****110
复制代码


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

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

  105生产了,而没有消费

  原因分析

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

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

  解决方案

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

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

  Resource.java

 
  1.  /**

  2.   * Created by yuandl on 2016-10-11./**

  3.   * 资源

  4.   */

  5.   public class Resource {

  6.   /*资源序号*/

  7.   private int number = 0;

  8.   /*资源标记*/

  9.   private boolean flag = false;

  10.   /**

  11.   * 生产资源

  12.   */

  13.   public synchronized void create() {

  14.   while (flag) {//先判断标记是否已经生产了,如果已经生产,等待消费;

  15.   try {

  16.   wait();//让生产线程等待

  17.   } catch (InterruptedException e) {

  18.   e.printStackTrace();

  19.   }

  20.   }

  21.   number++;//生产一个

  22.   System.out.println(Thread.currentThread().getName() + "生产者------------" + number);

  23.   flag = true;//将资源标记为已经生产

  24.   notify();//唤醒在等待操作资源的线程(队列)

  25.   }

  26.   /**

  27.   * 消费资源

  28.   */

  29.   public synchronized void destroy() {

  30.   while (!flag) {

  31.   try {

  32.   wait();

  33.   } catch (InterruptedException e) {

  34.   e.printStackTrace();

  35.   }

  36.   }

  37.   System.out.println(Thread.currentThread().getName() + "消费者****" + number);

  38.   flag = false;

  39.   notify();

  40.   }

  41.   }
复制代码


f.jpg


  再次发现问题

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

  原因分析

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

  解决方案

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

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

  Resource.java

  1.   /**

  2.   * Created by yuandl on 2016-10-11./**

  3.   * 资源

  4.   */

  5.   public class Resource {

  6.   /*资源序号*/

  7.   private int number = 0;

  8.   /*资源标记*/

  9.   private boolean flag = false;

  10.   /**

  11.   * 生产资源

  12.   */

  13.   public synchronized void create() {

  14.   while (flag) {//先判断标记是否已经生产了,如果已经生产,等待消费;

  15.   try {

  16.   wait();//让生产线程等待

  17.   } catch (InterruptedException e) {

  18.   e.printStackTrace();

  19.   }

  20.   }

  21.   number++;//生产一个

  22.   System.out.println(Thread.currentThread().getName() + "生产者------------" + number);

  23.   flag = true;//将资源标记为已经生产

  24.   notifyAll();//唤醒在等待操作资源的线程(队列)

  25.   }

  26.   /**

  27.   * 消费资源

  28.   */

  29.   public synchronized void destroy() {

  30.   while (!flag) {

  31.   try {

  32.   wait();

  33.   } catch (InterruptedException e) {

  34.   e.printStackTrace();

  35.   }

  36.   }

  37.   System.out.println(Thread.currentThread().getName() + "消费者****" + number);

  38.   flag = false;

  39.   notifyAll();

  40.   }

  41.   }
复制代码


  运行结果

  1.   Thread-0生产者------------412

  2.   Thread-2消费者****412

  3.   Thread-0生产者------------413

  4.   Thread-3消费者****413

  5.   Thread-1生产者------------414

  6.   Thread-2消费者****414

  7.   Thread-1生产者------------415

  8.   Thread-2消费者****415

  9.   Thread-0生产者------------416

  10.   Thread-3消费者****416

  11.   Thread-1生产者------------417

  12.   Thread-3消费者****417

  13.   Thread-0生产者------------418

  14.   Thread-2消费者****418

  15.   Thread-0生产者------------419

  16.   Thread-3消费者****419

  17.   Thread-1生产者------------420

  18.   Thread-2消费者****420
复制代码


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

原文作者:佚名  来源:开发者头条

相关帖子

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关于我们
联系我们
  • 电话:010-86393388
  • 邮件:udn@yonyou.com
  • 地址:北京市海淀区北清路68号
移动客户端下载
关注我们
  • 微信公众号:yonyouudn
  • 扫描右侧二维码关注我们
  • 专注企业互联网的技术社区
版权所有:用友网络科技股份有限公司82041 京ICP备05007539号-11 京公网网备安1101080209224 Powered by Discuz!
快速回复 返回列表 返回顶部