#什么是虚假唤醒
#为什么会出现虚假唤醒
#解决的方法
什么是虚假唤醒
如图,JavaJdk10API文档这样描述
意思就是说线程可以在没有被通知,中断或者超时的情况下被唤醒,这就是所谓的虚假唤醒
为什么会出现虚假唤醒
1.当Object.wait()这个方法运行时,当前的线程会进入等待状态,并自动释放锁。当被其他线程唤醒时,它会在wait()之后的地方继续开始运行
2.当Object.notifyAll运行时,会唤醒所有处于等待状态的线程同时进行抢夺锁,而导致两个线程会同时进行重写抢锁。
下面举个例子
package testJUC;
/**
* 测试java线程的虚假唤醒
*/
public class TestFalseAwaken {
public static void main(String[] args) {
Product product = new Product();
//创建2个线程运行
new Thread(() -> {
for (int i = 0; i < 20; i++)
new Producer2(product).getProduct();
}, "生产者A").start();
new Thread(() -> {
for (int i = 0; i < 20; i++)
new Consumer2(product).saleProduct();
}, "消费者A").start();
new Thread(() -> {
for (int i = 0; i < 20; i++)
new Producer2(product).getProduct();
}, "生产者B").start();
new Thread(() -> {
for (int i = 0; i < 20; i++)
new Consumer2(product).saleProduct();
}, "消费者B").start();
}
}
/**
* 产品
* 为了放大Java线程的虚假唤醒和方便测试结果,设置库存的容量为1
*/
class Product {
//库存
private int count = 0;
//生产鞋子
public synchronized void getProduct() {
if (count != 0) {
try {
System.out.println("库存已经满,正在通知消费者消费");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "生产了一个产品,剩余的库存->" + (++count));
this.notifyAll();
}
//卖出鞋子
public synchronized void saleProduct() {
if (count == 0) {
try {
System.out.println("库存不够,正在通知生产者生产");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "买了一个产品,剩余的库存->" + (--count));
this.notifyAll();
}
}
/**
* 生产者
*/
class Producer2 {
private Product product;
public Producer2(Product product) {
this.product = product;
}
//生产鞋子
public void getProduct() {
product.getProduct();
}
}
/**
* 消费者
*/
class Consumer2 {
private Product product;
public Consumer2(Product product) {
this.product = product;
}
//卖鞋子
public void saleProduct() {
product.saleProduct();
}
}
上述代码使用if判断,唤醒后线程会从wait之后的代码开始运行,但是不会重新判断if条件,直接继续运行if代码块之后的代码。
运行的结果可能出现下列情况
为什么会出现这种情况呢?
1.首先有四个线程: 生产者A,消费者A,生产者A,消费者B。
2.当多个生产者线程同时进入生产产品的方法时,而判断条件if只进行一次判断,就会出现的count值多次加1,导致出现这种结果。
解决的方法
意思就是说推荐的方法是以等待是检查被期待已久的条件放在一个While里面,循环到wait。简单的来说,就是把if改成while。
以本文所提供的代码为例,把if的判断改成while就可以有效解决此问题,改善的代码如下
package testJUC;
/**
* 测试java线程的虚假唤醒
*/
public class TestFalseAwaken {
public static void main(String[] args) {
Product product = new Product();
//创建2个线程运行
new Thread(() -> {
for (int i = 0; i < 20; i++)
new Producer2(product).getProduct();
}, "生产者A").start();
new Thread(() -> {
for (int i = 0; i < 20; i++)
new Consumer2(product).saleProduct();
}, "消费者A").start();
new Thread(() -> {
for (int i = 0; i < 20; i++)
new Producer2(product).getProduct();
}, "生产者B").start();
new Thread(() -> {
for (int i = 0; i < 20; i++)
new Consumer2(product).saleProduct();
}, "消费者B").start();
}
}
/**
* 产品
* 为了放大Java线程的虚假唤醒和方便测试结果,设置库存的容量为1
*/
class Product {
//库存
private int count = 0;
//生产鞋子
public synchronized void getProduct() {
while (count != 0) {
try {
System.out.println("库存已经满,正在通知消费者消费");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "生产了一个产品,剩余的库存->" + (++count));
this.notifyAll();
}
//卖出鞋子
public synchronized void saleProduct() {
while (count == 0) {
try {
System.out.println("库存不够,正在通知生产者生产");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "买了一个产品,剩余的库存->" + (--count));
this.notifyAll();
}
}
/**
* 生产者
*/
class Producer2 {
private Product product;
public Producer2(Product product) {
this.product = product;
}
//生产鞋子
public void getProduct() {
product.getProduct();
}
}
/**
* 消费者
*/
class Consumer2 {
private Product product;
public Consumer2(Product product) {
this.product = product;
}
//卖鞋子
public void saleProduct() {
product.saleProduct();
}
}
运行的结果如下