多线程交互虚假唤醒问题详解
1.虚假唤醒产生的原因
我们以经典的生产者消费者问题来演示,看以下代码
class Test {
private int number = 0;
//生产者方法
public synchronized void increment() throws InterruptedException {
//判断,当number>0时线程等待,等待消费者消费
if (number != 0) {
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName() + "\t" + number);
this.notifyAll();
}
//消费者方法
public synchronized void decrement() throws InterruptedException {
//判断,当number等于0时线程等待,等待生产者生产
if (number == 0) {
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName() + "\t" + number);
this.notifyAll();
}
}
public class ThreadWaitNotifyDemo {
public static void main(String[] args) {
Test test = new Test();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
Thread.sleep(200);
test.increment();
} catch (Exception e) {
}
}
}, "A").start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
Thread.sleep(300);
test.decrement();
} catch (Exception e) {
}
}
}, "B").start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
Thread.sleep(400);
test.increment();
} catch (Exception e) {
}
}
}, "C").start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
Thread.sleep(500);
test.decrement();
} catch (Exception e) {
}
}
}, "D").start();
}
}
在以上代码中,我们开了四个线程,其中两个消费者线程,两个生产者线程。
在运行代码后,我们预期的结果应该是number一直维持在0和1之间,但是当我们
运行代码后出现以下结果
我们发现number出现2的情况,这意味着进行了两次的++操作。
2.原因分析
我们考虑这样一种情况。当某一个生产者线程进行生产之后,及代码中number++之后,此时number=1,当前线程进入等待,即阻塞状态。此时我们应该期望的是一个消费者线程进来进行消费,但是此时又有一个生产者线程进来,此时number还是1,当前线程继续等待,意味着此时有两个生产者线程在等待。此时外面只剩下消费者线程,随意进来一个线程进行消费之后,当前number=0;此时就会唤醒两个等待中的生产者线程。由于用if作为判断语句,在两个线程被唤醒之前已经经过判断,所以两个线程都会执行number++操作。从而导致number=2的情况。因此在进行多线程交互时,尤其是两个以上的线程交互时,做判断的时候不应该用if,而应该用while。
3.问题解决
经过上述原因分析,我们明白在做判断时应当用while。我们修改之前的代码,将if改为while:
class Test {
private int number = 0;
//生产者方法
public synchronized void increment() throws InterruptedException {
//判断,当number>0时线程等待,等待消费者消费
while (number != 0) {
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName() + "\t" + number);
this.notifyAll();
}
//消费者方法
public synchronized void decrement() throws InterruptedException {
//判断,当number等于0时线程等待,等待生产者生产
while (number == 0) {
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName() + "\t" + number);
this.notifyAll();
}
}
public class ThreadWaitNotifyDemo {
public static void main(String[] args) {
Test test = new Test();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
Thread.sleep(200);
test.increment();
} catch (Exception e) {
}
}
}, "A").start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
Thread.sleep(300);
test.decrement();
} catch (Exception e) {
}
}
}, "B").start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
Thread.sleep(400);
test.increment();
} catch (Exception e) {
}
}
}, "C").start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
Thread.sleep(500);
test.decrement();
} catch (Exception e) {
}
}
}, "D").start();
}
}
运行代码,得到以下结果
运行代码,得到以下结果
我们发现没有2出现,说明问题解决。