线程的等待与唤醒
要点
- wait() 、 wait(long timeout)、wait(long timeout, int nanos) Object类的方法。让当前执行的线程等待,调用wait前必须要获取到锁,调用后释放锁。(抛出InterruptedException 见【线程的停止与中断】章节)
- notify()、notifyAll() Object类的方法,一个随机唤醒一个等待线程,一个唤醒所有等待的线程。调用前必须获取锁,通知后不一定会释放锁。且不抛出异常。
- 注: 本章中关于锁对象和synchronized关键字我们将在synchronized章节去讨论,不做过多介绍。
认识wait
首先我们应该要了解wait方法是Object类的方法,也就是说所有的java类都是可以调用这个方法的。包括后面的notify和notifyAll也是Object的方法。而且这两个方法都是final native。之所以这么设计是和synchronized整个线程有关 ,第一synchronized是可以监听任何对象的,所以wait和notify就让所有的对象可调用,而不是只能线程Thread类调用。
我们先看看wait三个重载方法的源代码,如下所示。
public final void wait() throws InterruptedException {
wait(0);
}
public final void wait(long timeout, int nanos) throws InterruptedException {
if (timeout < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos > 0) {
timeout++;
}
wait(timeout);
}
public final native void wait(long timeout) throws InterruptedException;
看完上面的源代码你会发现wait()、wait(long timeout, int nanos)最终都会去调用native修饰的wait(long timeout)方法。其中wait()方法是调用的wait(0); 会根据nanos值的大小来判断是否给timeout加一,然后在调用wait(timeout),个人觉得这个方法意义不是很大;总结一下就是调用wait可以让当前线程等待timeout毫秒,如果是0就无限制等待。至于怎么唤醒等待的线程,后面再说。先看看下面的小案例。
public class TestWaitNotify {
public static void main(String[] args) {
Object object = new Object();
try {
synchronized (object) {//注释synchronized关键字后会抛出java.lang.IllegalMonitorStateException
System.out.println("wiat前:"+System.currentTimeMillis());
object.wait(3);
System.out.println("wiat后:"+System.currentTimeMillis());
}//
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行上面小程序的时候先注释掉synchronized关键字,你会发现会抛出一个java.lang.IllegalMonitorStateException异常。也就是说wait没有获取到监听对象object的锁。我们加上后再次运行,你会发现前后打印的时间差肯定是大于3毫秒的。对于wait(0)我们怎么去唤醒呢,我们就需要结合notify来用了。
认识notify和notifyAll
notify和notifyAll前者是随机唤醒同一个监听对象上wait的线程,后者则是唤醒所有的线程。这两个方法与wait一样调用前也需要获取锁,所以要放在synchronized中。但是调用完之后,并不一定马上释放锁而是到等到当前方法执行退出synchronized的范围之后才释放。而wait方法调用后就会释放锁。
我们先来做一个案例验证一下notify的唤醒和wait方法调用后会释放锁的特性。
public class TestWaitNotify2 {
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (object) {
try {
// 放置子线程过早唤醒主线程
Thread.sleep(3000);
System.out.println("唤醒主线程");
object.notify();
// 放开测试notify并不一定释放锁,放开注释你会发现主线程没有马上被唤醒
// Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t1.start();
synchronized (object) {
System.out.println("main线程在等待");
object.wait();
System.out.println("main线程被唤醒了");
}
}
}
运行上面的小程序你会发现,wait之后,子程序的synchronized获取到了锁且成功唤醒了主线。接着我们把object.notify();后面的那么睡眠注释打开,你再次运行会发现主线程没有立马被唤醒,应为notify调用后它自己后面还在运行并么有释放锁,等到运行完之后锁释放,我们这里也就是睡眠3秒后才释放锁。
接下来我们验证一下notifyAll与notify的区别。先看下面案例源码。
public class TestWaitNotify3 extends Thread {
static Object object = new Object();
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
new TestWaitNotify3().start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (object) {
System.out.println("唤醒线程");
object.notify();
// object.notifyAll();
}
}
@Override
public void run() {
synchronized (object) {
try {
System.out.println(Thread.currentThread().getName() + " wait");
object.wait();
System.out.println(Thread.currentThread().getName() + " notify");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
我们在主线程中创建三个子线程,然后让它们wait。第一次运行时我们调用object.notify(),你会发现只要一个子线程被唤醒了其他两个还在等待且运行不会退出运行。第二次我们调用object.notifyAll(),结果三个线程都被唤醒了,程序运行完退出。