1 基本介绍
1.1 wait方法
wait和notify方法并不是Thread特有的方法,而是Object中的方法,也就是说在JDK中的每一个类都拥有这两个方法,那么这两个方法到底有什么神奇之处可以使线程阻塞又可以唤醒线程呢?我们先来说说wait方法,下面是wait方法的三个重载方法。
public final void wait() throws InterruptedException
// 指定等待超时时间
public final void wait(long timeout) throws InterruptedException
public final void wait(long timeout, int nanos) throws InterruptedException
-
wait方法的这三个重载方法都将调用wait(long timeout)这个方法,前文使用的wait()方法等价于wait(0),0代表着永不超时
-
Object的wait(long timeout)方法会导致当前线程进入阻塞,直到有其他线程调用了Object的notify或者notifyAll方法才能将其唤醒,或者阻塞时间到达了timeout时间而自动唤醒
-
wait方法必须拥有该对象的monitor,也就是wait方法必须在同步方法中使用。
-
当前线程执行了该对象的wait方法之后,将会放弃对该monitor的所有权并且进入与该对象关联的wait set中,也就是说一旦线程执行了某个object的wait方法之后,它就会释放对该对象monitor的所有权,其他线程也会有机会继续争抢该monitor的所有权。
上节所展示的eventQueue中的代码(上节地址),可以看到,由于有多个线程操作event-Queue,因此它自然而然就是共享资源,为了防止多线程对共享资源操作引起数据不一致的问题,我们需要对共享资源进行同步处理,这里使用synchronized关键字对其进行同步。如果当前队列event数量已经达到了上限,那么它会调用eventQueue的wait方法使当前线程进入wait set中并且释放monitor的锁。
public void submit(Event event) {
synchronized (eventQueue) {
if (eventQueue.size() >= this.max) {
// 如果队列中的事情已经大于等于最大值了,那么进入等待
try {
System.out.println("event queue has full");
eventQueue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " submit a new event has add to event queue " + event);
// 将事件添加到队列中
eventQueue.addLast(event);
// 唤醒工作线程
eventQueue.notify();
}
}
如果当前队列的数量还没有达到上限,则会将event插入队尾,并且企图唤醒已经加入wait set中的线程,由于我们使用的是单线程的插入和移除,因此在submit中执行的唤醒只能是执行get时被阻塞的线程
/**
* 从队列中获取事件
* @return
*/
public Event get(){
synchronized (eventQueue){
if (eventQueue.isEmpty()){
// 队列中没有事件,只好等待了
try {
eventQueue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 取出并删除事件
Event event = eventQueue.removeFirst();
// 唤醒
eventQueue.notify();
System.out.println(Thread.currentThread().getName() + " has get event: " + event);
return event;
}
}
1.2 notify
// 唤醒单个正在执行该对象wait方法的线程。
// 如果有某个线程由于执行该对象的wait方法而进入阻塞则会被唤醒,如果没有则会忽略。
// 被唤醒的线程需要重新获取对该对象所关联monitor的lock才能继续执行。
public final native void notify();
// 唤醒所有正在执行该对象wait方法的线程。
public final native void notifyAll();
1.3 这些方法是线程相关的,为甚么要定义在object类中
因为这些方法的调用是依赖于锁对象的,而同步代码块的锁对象是任意锁。
而Object代码任意的对象,所以,定义在这里面。因为等待还是唤醒都是由锁对象来负责的
2 关于wait和notify的注意事项
- wait方法是可中断方法,这也就意味着,当前线程一旦调用了wait方法进入阻塞状态,其他线程是可以使用interrupt方法将其打断的,可中断方法被打断后会收到中断异常InterruptedException,同时interrupt标识也会被擦除。
- 线程执行了某个对象的wait方法以后,会加入与之对应的wait set中,每一个对象的monitor都有一个与之关联的wait set
- 当线程进入wait set之后,notify方法可以将其唤醒,也就是从wait set中弹出,同时中断wait中的线程也会将其唤醒
- 必须在同步方法中使用wait和notify方法,因为执行wait和notify的前提条件是必须持有同步方法的monitor的所有权
- 同步代码的monitor必须与执行wait notify方法的对象一致,简单地说就是用哪个对象的monitor进行同步,就只能用哪个对象进行wait和notify操作