线程虚假唤醒问题

tech2025-04-20  8

线程虚假唤醒问题

在线程通信中可能存在虚假唤醒问题,关于虚假唤醒的概念,我觉得官方文档说得不太清晰,下面通过一个例子简单说明:

现在有两个方法,一个+1,一个-1,每个方法开启2个线程循环执行10次;

代码实现

public class Test { //开启4个线程 public static void main(String[] args) { Data data = new Data(); new Thread(()->{ for (int i = 0; i < 10; i++) { data.increment(); } },"A").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { data.decrement(); } },"B").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { data.increment(); } },"C").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { data.decrement(); } },"D").start(); } } class Data{ private int number = 0; //+1的方法 public synchronized void increment(){ if (number != 0){ try { //等待 this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } number++; System.out.println(Thread.currentThread().getName()+"=>"+number); //通知其他线程 this.notifyAll(); } //-1的方法 public synchronized void decrement(){ if (number == 0){ try { //等待 this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } number--; System.out.println(Thread.currentThread().getName()+"=>"+number); //通知其他线程 this.notifyAll(); } }

+1的方法中,判断number值是否为0,如果不为0调用线程等待的方法,如果为0 number值+1,控制台打印线程名称的number值,然后通知其他线程;-1的方法同理;

我们的预期是控制台交替打印40条记录,每条记录的结果是线程名 + 0或者1;

运行的结果:

A=>1 B=>0 A=>1 B=>0 A=>1 B=>0 A=>1 B=>0 A=>1 B=>0 A=>1 B=>0 C=>1 A=>2 C=>3 B=>2 B=>1 B=>0 C=>1 A=>2 C=>3 B=>2 D=>1 D=>0 C=>1 A=>2 C=>3 D=>2 D=>1 D=>0 C=>1 A=>2 C=>3 D=>2 D=>1 D=>0 C=>1 D=>0 C=>1 D=>0

结果发现number值出现2和3,这就是线程虚假唤醒造成的问题,那为什么会这样?官方文档是这样解释的:

我看着很头大,只知道要把+1和-1方法中的 if 换成while;

if 换while输入的结果

A=>1 B=>0 A=>1 B=>0 A=>1 B=>0 A=>1 B=>0 A=>1 B=>0 A=>1 B=>0 A=>1 B=>0 A=>1 B=>0 A=>1 B=>0 A=>1 B=>0 C=>1 D=>0 C=>1 D=>0 C=>1 D=>0 C=>1 D=>0 C=>1 D=>0 C=>1 D=>0 C=>1 D=>0 C=>1 D=>0 C=>1 D=>0 C=>1 D=>0

这样就可以了,那为什么呢?

if 语句的判断只会执行一次,当满足条件进入线程等待,线程被唤醒后直接执行下面的代码;

上面有段输入的记录是这样的:

D=>0 C=>1 A=>2 C=>3 B=>2

D线程执行完-1的方法后,C和A线程处同时被唤醒,都执行+1的方法,C线程先+1,所以A线程会输出2;

此时C线程比较猛,在A线程执行+1的方法时,它已经抢到cpu资源执行到线程等待这一步了,A线程执行完又把C线程唤醒,C线程执行number++,所以输入了3;

while 语句会一直循环判断,当满足条件进入线程等待,并且一直循环判断;

还是上面 if 语句 那段异常输入的记录,看看while会怎样执行;

D线程执行完-1的方法后,C和A线程同时被唤醒,此时C执行了+1的方法,number=1;现在用了while方法,A线程会循环判断 number != 0,此时条件成立,A线程继续进入线程等待;

所以不会有上面那种情况;

总结

当有两条线程调用相同的方法时,线程唤醒调用了notifyAll()方法,会唤醒所有线程,这两条线程都会被唤醒,如果用if会直接执行下一步的代码,如果用while,线程虽然被唤醒,但还是会进行循环判断,就避免了线程虚假唤醒的问题;

那我们使用notify()方法可不可,notify()方法是唤醒下一个线程,只唤醒一个;

还是上面的例子,当A线程执行完调用notify()唤醒其他线程,此时number = 1,如果唤醒的是C线程,C线程判断number != 0,进入线程等待,会一直卡在这里;所以用notify()方法是不行的;

实际开发中,我们也很少在一个方法中执行两条调用同个方法的线程(上面的例子如果每个方法只开启一条线程不会出现线程虚假唤醒的情况);在多线程中,常用while判断而不是if;

最新回复(0)