虚假唤醒:线程也可以唤醒,而不会被通知,中断或超时,即所谓的虚假唤醒 。 虽然这在实践中很少会发生,但应用程序必须通过测试应该使线程被唤醒的条件来防范,并且如果条件不满足则继续等待。(当线程只有一个消费者和一个生产者的时候不会发生,多个生产者和多个消费者同时进行if判断时才会发生)
举个简单例子,我们正常的生产者和消费者问题应该是:
//线程之间的通信问题:生产者和消费者问题!等待唤醒,通知唤醒线程交替执行A B操作同一个变量num = 0
// A num+1
// B num-1
public class A {
public static void main(String[] args) {
Data data=new Data();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
try {
data.product();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
try {
data.consumer();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
class Data{
private int num=0;
public synchronized void product() throws InterruptedException {
if (num != 0) {
wait();
}
num++;
System.out.println(Thread.currentThread()+"线程生产了1份,当前:"+num);
notifyAll();
}
public synchronized void consumer() throws InterruptedException {
if (num == 0) {
wait();
}
num--;
System.out.println(Thread.currentThread()+"线程消费了1份,当前:"+num);
notifyAll();
}
}
然后结果是这样的:
Thread[A,5,main]线程生产了1份,当前:1
Thread[B,5,main]线程消费了1份,当前:0
Thread[A,5,main]线程生产了1份,当前:1
Thread[B,5,main]线程消费了1份,当前:0
Thread[A,5,main]线程生产了1份,当前:1
Thread[B,5,main]线程消费了1份,当前:0
Thread[A,5,main]线程生产了1份,当前:1
Thread[B,5,main]线程消费了1份,当前:0
Thread[A,5,main]线程生产了1份,当前:1
Thread[B,5,main]线程消费了1份,当前:0
Thread[A,5,main]线程生产了1份,当前:1
Thread[B,5,main]线程消费了1份,当前:0
Thread[A,5,main]线程生产了1份,当前:1
但是我们这里采用的是if来判断条件这时候会存在虚假唤醒,由于上面的例子只有一个生产者一个消费者所以没问题,下面演示多个的情况:
//线程之间的通信问题:生产者和消费者问题!等待唤醒,通知唤醒线程交替执行A B操作同一个变量num = 0
// A num+1
// B num-1
public class A {
public static void main(String[] args) {
Data data=new Data();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
try {
data.product();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
try {
data.consumer();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
try {
data.product();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
try {
data.consumer();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
class Data{
private int num=0;
public synchronized void product() throws InterruptedException {
if (num != 0) {
wait();
}
num++;
System.out.println(Thread.currentThread()+"线程生产了1份,当前:"+num);
notifyAll();
}
public synchronized void consumer() throws InterruptedException {
if (num == 0) {
wait();
}
num--;
System.out.println(Thread.currentThread()+"线程消费了1份,当前:"+num);
notifyAll();
}
}
此时会出现结果:
Thread[B,5,main]线程消费了1份,当前:0
Thread[A,5,main]线程生产了1份,当前:1
Thread[C,5,main]线程生产了1份,当前:2
Thread[A,5,main]线程生产了1份,当前:3
Thread[B,5,main]线程消费了1份,当前:2
Thread[B,5,main]线程消费了1份,当前:1
Thread[B,5,main]线程消费了1份,当前:0
Thread[A,5,main]线程生产了1份,当前:1
Thread[C,5,main]线程生产了1份,当前:2
Thread[A,5,main]线程生产了1份,当前:3
Thread[B,5,main]线程消费了1份,当前:2
Thread[B,5,main]线程消费了1份,当前:1
Thread[A,5,main]线程生产了1份,当前:2
Thread[C,5,main]线程生产了1份,当前:3
Thread[A,5,main]线程生产了1份,当前:4
那么怎么处理虚假唤醒这个问题呢?翻阅jdk的api发现:
换句话说,等待应该总是出现在循环中,就像这样:
synchronized (obj) {
while (<condition does not hold>)
obj.wait(timeout);
... // Perform action appropriate to condition
}
我们将if改为while在尝试一次:
//线程之间的通信问题:生产者和消费者问题!等待唤醒,通知唤醒线程交替执行A B操作同一个变量num = 0
// A num+1
// B num-1
public class A {
public static void main(String[] args) {
Data data=new Data();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
try {
data.product();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
try {
data.consumer();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
try {
data.product();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
try {
data.consumer();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
class Data{
private int num=0;
public synchronized void product() throws InterruptedException {
while (num != 0) {
wait();
}
num++;
System.out.println(Thread.currentThread()+"线程生产了1份,当前:"+num);
notifyAll();
}
public synchronized void consumer() throws InterruptedException {
while (num == 0) {
wait();
}
num--;
System.out.println(Thread.currentThread()+"线程消费了1份,当前:"+num);
notifyAll();
}
}
虚假唤醒问题被解决了:
Thread[B,5,main]线程消费了1份,当前:0
Thread[C,5,main]线程生产了1份,当前:1
Thread[B,5,main]线程消费了1份,当前:0
Thread[A,5,main]线程生产了1份,当前:1
Thread[B,5,main]线程消费了1份,当前:0
Thread[C,5,main]线程生产了1份,当前:1
Thread[B,5,main]线程消费了1份,当前:0
Thread[A,5,main]线程生产了1份,当前:1
Thread[B,5,main]线程消费了1份,当前:0
Thread[C,5,main]线程生产了1份,当前:1
Thread[B,5,main]线程消费了1份,当前:0
Thread[A,5,main]线程生产了1份,当前:1
Thread[B,5,main]线程消费了1份,当前:0
Thread[C,5,main]线程生产了1份,当前:1
Thread[B,5,main]线程消费了1份,当前:0
Thread[A,5,main]线程生产了1份,当前:1
Thread[B,5,main]线程消费了1份,当前:0
Thread[C,5,main]线程生产了1份,当前:1
Thread[B,5,main]线程消费了1份,当前:0
为什么会出现这样的结果,我们拿消费者来举例,假如存在虚假唤醒,那么此时的num==0,就进入等待,一直等到num不是虚假唤醒而是真实唤醒num变成了1
结论:判断用while别用if