1. 概述
在上一节Java多线程设计(二)线程的基本知识(2)共享互斥中,当有一个线程在执行synchronized实例方法时,其它线程就无法执行该方法。这是简单的共享互斥。
假设现在我们想做进一步的处理,例如我现在有一个买烧鸡的店,我想只要店里还有烧鸡,我就让厨师等待着,不要做烧鸡先了,当消费者来买完我的烧鸡,我就通知厨师又开始做烧鸡。
上面的情景可以用Java中的wait
、notify
和notifyAll
三个方法处理。wait
方法是让线程乖乖等待,notify
和notifyAll
方法是唤醒等待中的线程。下面就来详细说一番。
2. wait set——线程的休息室
在开始讲解wait、notify和notifyAll之前,先稍微介绍一下wait set
。所有的实例都有一个wait set,wait set是一个在执行该实例的wait方法时,操作停止的线程的集合,它有点像是线程的休息室。
一执行wait方法,线程便会暂时停止操作,进入wait set这个休息室,除非发生下列中的其中一种情况,否则线程会拥有被留在这个wait set里。当发生下列任一情况时,线程便会离开wait set。
- 有其他线程用notify方法唤醒该线程;
- 有其他线程以notifyAll方法唤醒该线程;
- 有其他线程以interrupt方法唤醒该线程;
- wait方法到期。
3. wait方法——把线程放入wait set
假设一个线程执行了如下的语句:
obj.wait();
则这个线程就会暂时停止执行,进入实例obj的wait set。这个操作称为“线程在obj上wait”。当然了,如果一个线程执行了实例方法中的wait()
方法(等同于this.wait()
)方法,则该线程就会进入this的wait set。此时变成了线程在this上wait。
需要注意的是,wait方法要在同步方法或者同步代码块中执行,不然会抛一个IllegalMonitorStateException
异常。
还需要注意的是,wait set只是一个虚拟的概念,不是一个实例的字段,更不是可以获取在实例上wait着的线程列表方法。
下面我画画简单的例子图
(1)获取锁的线程A开始执行wait方法,此时线程B被阻挡着,不能执行synchronized方法
(2)线程A执行完wait方法,进入wait set,并释放锁
(3)线程B获取锁,开始执行synchronized方法
4. notify方法——从wait set拿出线程
使用notify
方法时,可以从wait set里抓出1个线程。假设现在执行了下面语句
obj.notify();
则从obj的wait set里的线程中挑1个,唤醒这个线程,被唤醒的这个线程便退出wait set。
(1)继续上面的流程,假设线程B执行的synchronized方法中调用了notify
方法,则线程A会被唤醒并且准备继续执行刚刚的wait方法后面的代码,但是注意了,此时线程B还没释放锁,所以线程A并不能马上执行wait方法后面的代码
(2)当线程B执行完synchronized方法后,就会释放锁,此时线程A就可以继续执行wait方法后面的代码了,很简单的一个意思,图我就不画了。
注意了,notify
方法和wait
方法一样,必须得在同步方法或者同步代码块中执行,不然会抛一个IllegalMonitorStateException
异常。
还要注意了,上面的例子也有说到,就是一个线程被唤醒,并不会马上开始执行代码,因为执行notify
方法的线程还没释放锁。
那么要是wait set中的线程不止一个呢,notify
会唤醒哪一个?这个没有说指定哪个,也没有说怎么选择,姑且就说随机一个吧。
5. notifyAll——从wait set拿出所有线程
notifyAll
方法会把所有在wait set中线程唤醒。比如执行下面代码
obj.notifyAll();
则会唤醒实例obj的wait set中所有线程。
注意了,notifyAll
方法和notify
方法以及wait
方法一样,必须得在同步方法或者同步代码块中执行,不然会抛一个IllegalMonitorStateException
异常。至于原因,大家可以去这里看看为何wait需要在同步代码块中调用
notify
方法和notifyAll
方法很类似,就是在唤醒线程的数目上有很大差别,前者是只唤醒一个,或者是唤醒全部。
6. wait、notify、notifyAll是Object类方法
wait、notify、notifyAll方法都是java.lang.Object
类的方法,不是Thread
类独有的方法。
再来看一下这三个方法的作用:
- obj.wait()是把当前线程放到obj的wait set中;
- obj.notify()是从obj的wait set中唤醒一个线程;
- obj.notifyAll()是唤醒所有在obj的wait set中的线程。
大家可以注意到,这三个方法其实是对obj的wait set进行增、删操作,由于所有的实例都有wait set,所以这三个方法才会是Object类的方法。