Java多线程(2)生产者消费者问题(一)

一、问题描述

在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。如何用代码描述此类问题。

二、一个消费者线程、一个生产者线程

有几点需要强调:
1.main方法中的资源res的线程t1、t2共享的。所以当操作该资源时需要同步
2.同步函数的锁即为this,所以res即为线程t1、t2的锁
3.因为只有两个线程所以能够准确的唤醒对象线程(即消费者线程唤醒生产者进程、生产者进程唤醒消费者进程),
那么问题就来了,如果是多消费者进程,多生产进程怎么办?

package com.thread.pcprob;

/**
 * 共享数据
 * @author dqf
 *
 */
public class Resource {
       //产品编号
       private Integer proNo = 0;
       //判断是消费数据,还是生产数据
       private boolean flag = false;

       //生产数据
       public synchronized void produce(){
             if(flag){//如果flag=true,则等待消费;
                    try {
                          this.wait();
                    } catch (InterruptedException e) {
                          e.printStackTrace();
                    }
             }
             System.out.println("生产-->" + (++proNo));
             flag = true;
             this.notify();//唤醒等待进程
       }

       //消费数据
       public synchronized void consume(){
             if(!flag){//如果flag=false,则等待生产
                    try {
                          this.wait();
                    } catch (InterruptedException e) {
                          e.printStackTrace();
                    }
             }
             System.out.println("消费-------->" + proNo);
             flag = false;
             this.notify();//唤醒等待进程
       }
}
package com.thread.pcprob;

/**
 * 生产者
 * @author dqf
 *
 */
public class Producer implements Runnable{

       private Resource res;

       public Producer(Resource res) {
             this.res = res;
       }

       @Override
       public void run() {
             while(true){
                    res.produce();
             }
       }
}
package com.thread.pcprob;

/**
 * 消费者
 * @author dqf
 *
 */
public class Consumer implements Runnable{

       private Resource res;

       public Consumer(Resource res) {
             this.res = res;
       }

       @Override
       public void run() {
             while(true){
                    res.consume();
             }
       }
}
package com.thread.pcprob;

public class App {
       public static void main(String[] args) {
             Resource res = new Resource();

             Producer p = new Producer(res);
             Consumer c = new Consumer(res);

             Thread t1 = new Thread(p);
             Thread t2 = new Thread(c);

             t1.start();
             t2.start();

       }
}

部分输出:

消费-------->159091
生产-->159092
消费-------->159092
生产-->159093
消费-------->159093
生产-->159094
消费-------->159094
生产-->159095
消费-------->159095
生产-->159096
消费-------->159096
生产-->159097
消费-------->159097
生产-->159098
消费-------->159098
生产-->159099
消费-------->159099

三、多生产者进程、多消费者进程

我们把main方法修改如下:

public class App {
       public static void main(String[] args) {
             Resource res = new Resource();

             Producer p = new Producer(res);
             Consumer c = new Consumer(res);

             Thread t1 = new Thread(p);
             Thread t2 = new Thread(p);

             Thread t3 = new Thread(c);
             Thread t4 = new Thread(c);

             t1.start();
             t2.start();
             t3.start();
             t4.start();

       }
}

部分输出:

消费-------->123384
消费-------->123384
生产-->123385
消费-------->123385
消费-------->123385
生产-->123386
消费-------->123386
消费-------->123386
生产-->123387
消费-------->123387
消费-------->123387
生产-->123388
消费-------->123388

我们发现会出现两种错误的情况
1.生产一个,消费多次
2.生产多次,消费一次

下面来分析产生两种问题的原因:
这里写图片描述

假设一种状态:
flag = false,t1获取到锁,t2、t3、t4等待锁释放。
因为flag为false,t1阻塞自动释放锁,t2获取锁,同样因为flag为false,t2阻塞自动释放锁。
若t3获取锁,执行到this.notify(),则会唤醒一个阻塞状态的线程,如果唤醒了t1线程,t1线程结束后又唤醒了t2。
则,就会出现多次生产,一次消费的情况。

其实造成这种情况的主要原因是:不能指定具体唤醒哪一个线程、或者说不能指定唤醒哪一类线程。导致,生产进程结束后,又唤醒了生产进程;或者消费进程结束后,又唤醒消费进程。


如果将Resource改为(if改为while),那么在线程被唤醒后,都会再次判断一次flag。

package com.thread.pcprob;

/**
 * 共享数据
 * @author dqf
 *
 */
public class Resource {
       //产品编号
       private Integer proNo = 0;
       //判断是消费数据,还是生产数据
       private boolean flag = false;

       //生产数据
       public synchronized void produce(){
             while(flag){//如果flag=true,则等待消费;
                    try {
                          this.wait();
                    } catch (InterruptedException e) {
                          e.printStackTrace();
                    }
             }
             System.out.println("生产-->" + (++proNo));
             flag = true;
             this.notify();//唤醒等待进程
       }

       //消费数据
       public synchronized void consume(){
             while(!flag){//如果flag=false,则等待生产
                    try {
                          this.wait();
                    } catch (InterruptedException e) {
                          e.printStackTrace();
                    }
             }
             System.out.println("消费-------->" + proNo);
             flag = false;
             this.notify();//唤醒等待进程
       }
}

但是这种情况又会导致,t1、t2、t3、t4全部处于阻塞状态。

最终写法(notify改为notifyAll):

package com.thread.pcprob;

/**
 * 共享数据
 * @author dqf
 *
 */
public class Resource {
       //产品编号
       private Integer proNo = 0;
       //判断是消费数据,还是生产数据
       private boolean flag = false;

       //生产数据
       public synchronized void produce(){
             while(flag){//如果flag=true,则等待消费;
                    try {
                          this.wait();
                    } catch (InterruptedException e) {
                          e.printStackTrace();
                    }
             }
             System.out.println("生产-->" + (++proNo));
             flag = true;
             this.notifyAll();//唤醒等待进程
       }

       //消费数据
       public synchronized void consume(){
             while(!flag){//如果flag=false,则等待生产
                    try {
                          this.wait();
                    } catch (InterruptedException e) {
                          e.printStackTrace();
                    }
             }
             System.out.println("消费-------->" + proNo);
             flag = false;
             this.notifyAll();//唤醒等待进程
       }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值