Java多线程之生产者消费者问题(等待唤醒机制、虚假唤醒、锁机制)
本篇博客由浅入深,先从最基础的生产者消费者问题讲起,然后依次深入
普通的生产者消费者问题
首先我们有一个店员类,模拟进货和卖货操作,店里的商品数量最多为10个
注意这里我们给进货和卖货方法都加了synchronized的关键字
生产者线程,负责生产商品
消费者线程,负责消费商品
好,然后我们先看运行结果,然后我们来仔细分析运行结果并提出改正方案
以下是一个简单的测试类,首先new 了一个营业员,然后分别创建了一个消费者和生产者线程
执行结果如下:
结果分析:
通过分析以上运行结果我们可以发现一个很明显的结果,要么先是一大堆生产者集中生产,然后消费者集中消费,而且不管是生产者还是消费者,当商品已满或已进销售完毕之后,他们还在尝试生产和消费。
好,那为什么会造成这样的结果呢?原因就在于我们的店员类的进货和销售方法都带了synchronized 关键字,并且在消费者和生产者线程中分别执行了10的循环,这就导致不论是生产者还是消费者一抢到资源,就执行循环,导致出现集中的生产和消费。
采用等待唤醒机制的生产者消费者问题
添加等待唤醒机制的生产者消费者问题与之前不同的是,增加以下红框中的代码,它的意思是当商品已满时生产者等待,唤醒消费者线程消费,当商品销售完了之后,消费者等待,通知生产者生产,为了放大错误发生的几率,我们对将最大商品数量设置为1,并为生产者线程加了0.2秒的延迟
那我们来看采用了等待唤醒机制的生产者消费者问题的执行结果
结果分析:
首先我们看到运行的结果是很规律的,你生产一个,我消费一个,但是我们仔细观察的话我们会发现一个问题,那就是程序迟迟没有自动终止,这是为什么呢?
原因在于:我们的this.wait()和this.notifyAll()的操作没有成对出现,我们仔细观察我们的代码,如下:我们对等待操作和唤醒操作采用的是if,else的判断语句来执行,这就导致为什么程序无法终止的原因。好,可能你还不明白,那我再讲得简单一点,你仔细想一下,我的生产者和消费者线程中分别有10次的循环,去访问这些公共的商品,但是他们执行的顺序要么是生产者等待,消费者唤醒或者消费者等待,生产者唤醒,这样的执行顺序下去,就会导致什么,就会导致,到最后的时候就会有很大地概率,只有一个线程在那边wait等待,但是其他的线程都执行完了,没有人唤醒它了,所以就会出现程序无法终止的情况。
那如何解决呢?很简单,只要将if,else去掉就行了,代码如下:
虚假唤醒
好,那么到这里问题就都解决了吗?不,没有解决,试想一下,我们之前的测试和操作,采用的都只有一个生产者和一个消费者,那么如果我们有多个生产者和多个消费者呢?这个时候就会出现一个著名的问题叫虚假唤醒
测试代码如下 我们创建了多个生产者和消费者线程
public class TestProductorAndConsumer {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer producter = new Producer(clerk);
Consumer consumer = new Consumer(clerk);
new Thread(producter,"生产者A").start();
new Thread(consumer,"消费者A").start();
new Thread(producter,"生产者B").start();
new Thread(consumer,"消费者B").start();
}
}
运行结果如下:
结果分析:
分析结果你会发现结果已经严重的出错了,并且出现了很多的负数,问题出在哪里呢?
我们回到之前的进货和销售方法,我们已经知道他们是两个synchronized方法
/** * 进货操作 */
public synchronized void purchase() throws InterruptedException {
if (product >= 1) {
System.out.println("产品已满!");
this.wait();
}
System.out.println(Thread.currentThread().getName() + ":" + ++product);
this.notifyAll();
}
/** * 卖货操作 */
public synchronized void sale() throws InterruptedException {
if (product <= 0) {
System.out.println("缺货!!");
this.wait();
}
System.out.println(Thread.currentThread().getName()+":" + --product);
this.notifyAll();
}
由于我们有多个生产者和多个消费者,那么我们先假设在执行卖货方法sale()
方法时,有多个消费者A,B都在外面等待,首先是消费者A进行开始消费,但是它发现店内没货了,于是挂起等待,然后下一个消费者B进入消费,同样地也发现没货,因此也wait等待,之后出现了一个生产者,它生产了一个商品,然后执行了notifyAll()方法唤醒了正在等待的消费者线程们,它们一唤醒,就从原来挂起的位置继续执行下去,因此商品数量就被连续减了两次,就出现负数的运行结果。因此我们就称这种现象为虚假唤醒现象
那如何解决虚假唤醒的现象呢,看如下代码:
/** * 进货操作 */
public synchronized void purchase() throws InterruptedException {
while (product >= 1) {//避免虚假唤醒,使用while循环做判断
System.out.println("产品已满!");
this.wait();
}
System.out.println(Thread.currentThread().getName() + ":" + ++product);
this.notifyAll();
}
/** * 卖货操作 */
public synchronized void sale() throws InterruptedException {
while (product <= 0) {//避免虚假操作,使用while循环做判断
System.out.println("缺货!!");
this.wait();
}
System.out.println(Thread.currentThread().getName()+":" + --product);
this.notifyAll();
}
咦,你会发现这和之前的代码也没什么区别呀,注意,仔细看我们这里将if判断改为了while循环判断,这就能解决虚假唤醒的问题,我们再看运行结果,就正常了