虚假唤醒
原创文章,转载请注明原文章地址,谢谢!
生产者消费者案例
我们先用经典的生产者消费者案例来引出问题。
//产品
public class Clerk {
private int product = 0;
//生产产品
public synchronized void get() {
if (product >= 10) {
System.out.println("产品已满!");
} else {
System.out.println(Thread.currentThread().getName() + " : " + ++product);
}
}
//销售产品
public synchronized void sale() {
if (product <= 0) {
System.out.println("产品售空!");
} else {
System.out.println(Thread.currentThread().getName() + " : " + --product);
}
}
}
//生产者
public class Producer implements Runnable {
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
clerk.get();
}
}
}
//消费者
public class Consumer implements Runnable {
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
clerk.sale();
}
}
}
//测试
public class ProducerAndConsumerForLockTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer producer = new Producer(clerk);
Consumer consumer = new Consumer(clerk);
new Thread(producer, "生产者 A").start();
new Thread(consumer, "消费者 B").start();
}
}
测试结果
生产者 A : 1
生产者 A : 2
生产者 A : 3
生产者 A : 4
生产者 A : 5
生产者 A : 6
生产者 A : 7
生产者 A : 8
生产者 A : 9
生产者 A : 10
产品已满!
产品已满!
产品已满!
产品已满!
产品已满!
产品已满!
产品已满!
产品已满!
产品已满!
产品已满!
消费者 B : 9
消费者 B : 8
消费者 B : 7
消费者 B : 6
消费者 B : 5
消费者 B : 4
消费者 B : 3
消费者 B : 2
消费者 B : 1
消费者 B : 0
产品售空!
产品售空!
产品售空!
产品售空!
产品售空!
产品售空!
产品售空!
产品售空!
产品售空!
产品售空!
通过上述案例,我们发现,在生产产品达到上限的时候,还在不断的生产,而在销售产品已经售空的情况下,依然在不停的销售产品,那么此时就出现了问题。那如何解决这样的问题呢?这就要使用到下面的等待唤醒机制。
等待唤醒机制
通过上面的案例,我们在这里引入等待唤醒机制。所谓等待唤醒机制,就是当一个线程在执行了某一个操作的时候,将其进入等待状态,并释放锁,其他线程执行完指定的操作后,再将其唤醒。
public class Clerk {
private int product = 0;
//生产产品
public synchronized void get() {
if (product >= 1) {
System.out.println("产品已满!");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
System.out.println(Thread.currentThread().getName() + " : " + ++product);
this.notifyAll();
}
}
//销售产品
public synchronized void sale() {
if (product <= 0) {
System.out.println("产品售空!");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
System.out.println(Thread.currentThread().getName() + " : " + --product);
this.notifyAll();
}
}
}
测试结果
产品售空!
生产者 A : 1
产品已满!
消费者 B : 0
产品售空!
生产者 A : 1
产品已满!
消费者 B : 0
产品售空!
生产者 A : 1
产品已满!
消费者 B : 0
......
此时从结果中可以看出,还算是比较和谐的一个结果。但是我发现一个问题,就是我在运行多次的时候,出现了不一样的结果,其中有一种情况就是,程序一直没有停止,我们来演示一下这个现象。
//生产者
public class Producer implements Runnable {
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.get();
}
}
}
程序运行结果发现,程序确实一直没有停止,那这是什么原因呢?这里的改动只不过是将生产者中的run方法,阻塞了200ms。
我们假设现在产品为0,消费者剩下最后一次循环,生产者剩下2次循环。
- 消费者抢到锁,执行sale方法,此时产品售空,然后执行wait方法,消费者线程被挂起,并释放锁。
- 生产者循环次数为2,消费者剩余循环次数为0,产品数量为0。
- 生产者拿到锁,执行get方法,生产产品,并执行notifyAll方法,唤醒消费者线程。
- 生产者循环次数为1,消费者剩余循环次数为0,产品数量为1。
- 此时消费者线程被唤醒,这里要注意,被唤醒之后,它是从上次被挂起的地方,继续往下执行,而在本程序中,往下执行将不会进行其他操作,消费者线程结束。
- 生产者拿到锁,执行get方法,此时产品已满,然后执行wait方法,生产者线程被挂起,并释放锁。但是此时消费者线程已经结束,将再也没有线程来唤醒生产者线程,所以程序一直处于运行状态。
其实解决这个问题也很容易,我们将程序中的else语句去掉即可。
虚假唤醒问题
上面的案例,我们使用等待唤醒机制,似乎已经解决了问题,但是我们在上述案例中的测试中,只是使用了两个线程,那如果我们使用多个线程,会不会出现问题呢?
public class ProducerAndConsumerForLockTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer producer = new Producer(clerk);
Consumer consumer = new Consumer(clerk);
new Thread(producer, "生产者 A").start();
new Thread(consumer, "消费者 B").start();
new Thread(producer, "生产者 C").start();
new Thread(consumer, "消费者 D").start();
}
}
测试结果
产品售空!
产品售空!
生产者 A : 1
产品已满!
消费者 D : 0
产品售空!
消费者 B : -1
产品售空!
消费者 D : -2
产品售空!
......
我们已经发现,已经出现了负数,显然已经出现了问题。我们来简单分析一下原因。其实很好理解,当两个消费者线程同时执行sale方法时,产品售空,那么都将执行wait方法,处于挂起等待状态,并释放锁,然后生产者拿到锁,生产产品,执行notifyAll方法,唤醒了所有消费者线程,那么当第一个消费者执行了消费以后,第二个消费者又进行消费,此时便出现了负数,出现了问题。像这样的情况,就叫做虚假唤醒。
那么如何解决这个问题呢?我们只需要将判断的if换成while即可。换句话说,为了避免虚假唤醒问题,应该将判断一直使用在循环中。
//产品
public class Clerk {
private int product = 0;
//生产产品
public synchronized void get() {
while (product >= 1) {
System.out.println("产品已满!");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " : " + ++product);
this.notifyAll();
}
//销售产品
public synchronized void sale() {
while (product <= 0) {
System.out.println("产品售空!");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " : " + --product);
this.notifyAll();
}
}
测试结果
生产者 A : 1
产品已满!
产品已满!
消费者 D : 0
产品售空!
产品售空!
生产者 C : 1
产品已满!
产品已满!
消费者 B : 0
产品售空!
产品售空!
......
博客内容仅供自已学习以及学习过程的记录,如有侵权,请联系我删除,谢谢!