JUC多线程并发——生产者消费者(wait、notify)


可以把资源类看作是一个面包店,把成员变量看作是面包。

这个例子只有一个顾客(消费者)、一个烘焙师(生产者)。

以下代码使用synchronized关键字修饰方法,给资源上锁。

//线程操作资源类,首先创建资源类
class Resource{
    private int num = 0;
    public synchronized void produce() throws InterruptedException {
        if(num!=0){ //判断:如果还有资源,就等待消费者消费完通知
            this.wait();
        }
        num++;  //干活:初始值为0,没有资源就生产资源
        System.out.println(Thread.currentThread().getName()+"\t"+num);
        this.notifyAll();   //通知:生产完资源通知消费者
    }
    public synchronized void consump() throws InterruptedException {
        if(num==0){ //判断:初始值为0,没有资源,则等待生产者通知有资源了
            this.wait();
        }
        num--; //干活:有资源,消费者消费资源
        System.out.println(Thread.currentThread().getName()+"\t"+num);
        this.notifyAll();   //通知:消费完资源通知生产者
    }

}

//两个线程操作初始值为零的变量,一个线程对变量+1,一个线程对变量-1,交替10轮,变量的值为0
//判断,干活,通知
public class ThreadWaitNotify {
    public static void main(String[] args) {
        Resource resource = new Resource(); //先创建资源类的对象,由生产者和消费者共享
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    resource.produce(); //A线程相当于生产者,调用资源类的生产方法
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start(); //A线程进入就绪状态

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    resource.consump(); //B线程相当于生产者,调用资源类的消费方法
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start(); //B线程进入就绪状态
    }
}

运行结果:

此时又添加了一个生产者、一个消费者 ,现在一共两个生产者、两个消费者,共四个线程。

随着线程数量的增加,此时会发现出现一下情况。

这就是所谓的“虚假唤醒”问题。

 这是因为在wait那一步出现了问题,生产者线程wait被消费者线程唤醒后,没有通过判断 直接num++了,导致出现了2和3的情况。

由于wait会释放当前持有的对象的锁资源,如果在这个生产者线程wait过程中,有其他线程占用并生产了资源的话,那么此时num!=0,生产者线程应该继续wait,而不是num++

if(num!=0){ //判断:如果还有资源,就等待消费者消费完通知
            this.wait();
}

所以这一步应该使用while循环,在wait唤醒后继续判断。

while(num!=0){ //判断:如果还有资源,就等待消费者消费完通知
            this.wait();
}

再次运行的结果:

出现0和1才是想要的正确结果??

这是我学习过程中分享的第一篇技术博客,虽然仅仅是一个例子,但也代表着新的开始。

如有不足还请指正。

相关