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才是想要的正确结果??
这是我学习过程中分享的第一篇技术博客,虽然仅仅是一个例子,但也代表着新的开始。
如有不足还请指正。