我们都知道在Java多线程里面,wait,notify,notifyAll,是用来做线程之间的通信使用的,它们的作用如下:
wait方法:告诉当前线程,释放锁,然后开始睡眠等待,此时的状态为Watting,直到有线程进入一样的监视器调用notify或者notifyAll唤醒它
notify方法:随机唤醒一个在一样的对象监视器上等待的线程
notifyAll方法:唤醒所有的在一样对象监视器上等待的线程
关于线程通信,这里面最经典的案例就是生产者-消费者模式,这里有一些关于这三个方法的很有意思问题,我们来看一下:
(1)为什么这三个方法定义在Object类里面而不是在Thread类中?
这其实跟Java的锁机制有关系,Java允许任何对象都可以成为一个锁也叫做对象监视器,监视器本身是一种信号量,对于信号量应该是共享的用来互斥或者线程通信的,如果把这三个方法定义在线程类里面,那就意味着不同的线程需要相互侵入才能完成通信,比如A线程调用了自己的wait方法,然后它需要告诉B线程,你可以工作了,这就是典型的侵入依赖,其实A线程可以不用知道其他任何的线程,它只需要告诉监视器自己睡眠了,然后监视器自己去通知其他的一样使用该监视器的线程该干工作了就可以了。这有点类似大街上的红绿灯,我们完全不需要知道其他的车到底是在谁开,只需要看信号的通知即可。
(2)为什么这三个方法必须出现在同步方法或同步块中?
这其实很容易理解,首先为什么需要同步? 因为可能有多个线程同时操作共享变量,导致冲突,所以我们需要一个临界区,来保证每次只能有一个线程执行。也就是说wait,notify,notifyAll存在的时候肯定是会发生data race(数据竞争),在Java里面如果发生数据竞争肯定是需要同步的,所以这三个方法如果要出现那么一定是在同步的时候。如果你不在同步块里面调用这三个方法,那么将会抛出不合法监视器状态异常:
java.lang.IllegalMonitorStateException
(3)为什么在wait中,通常是在一个while循环中而不是使用if语句? 如下常见的模板:
synchronized (lock){
while ( condition ){
wait(); //release lock and waiting
}
//do something
}
这里面使用while而不是if语句的目的是为了避免出现一些微妙的bug,因为在调用了wait方法中,理论上该线程是进入休眠状态的,但是由于cpu可能会存在虚假唤醒的情况,如果我们使用if语句,那么当出现伪造唤醒的时候,程序就可能会出现一些异常,比如队列已经满了,生产线程应该休眠,等待消费线程消费,但由于休眠被虚假唤醒,然后继续生产,那么就会导致发生异常,这里如果使用while语句,将会确保即使发生虚假唤醒,也会根据条件判断是否合格,如果不合适就让其再次进入wait状态,从而保证我们的程序更加健壮。
关于wait,notify,notifyAll的使用例子,我已经更新到了我的github上,感兴趣的同学,可以去fork学习。
https://github.com/qindongliang/Java-Note
这个项目主要是我记录Java相关的学习笔记,包含了Java里面一些基础或者常见的知识如字符串和多线程并发相关等,并且在不断更新中。