在很多讲 wait(long timeout)的例子,都会用下面类似的代码:
public class RunA implements Runnable {
private Object lock;
public RunA(Object lock) {
this.lock = lock;
}
@Override
public void run() {
synchronized (lock){
try {
System.out.println("A begin");
// lock.wait(); // 永远等待着,不会执行下去
lock.wait(2000);// 等待了2秒之后,继续执行下去
System.out.println("A end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
举这样的例子显然是没有任何意义的,在这里用 wait(2000)和 sleep(2000) 有什么区别呢?
wait 和 sleep 显然是有很大的区别,但区别不只是 wait 会把 lock 释放掉,然我们引入一个新的搅和线程 B
public class RunB implements Runnable {
private Object lock;
public RunB(Object lock) {
this.lock = lock;
}
@Override
public void run() {
synchronized (lock) {
System.out.println("b come");
while (true) {
}
}
}
}
B 仅仅是握住锁,然后永远不释放,然后回到我们的主舞台 main 函数:
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
Thread threadA = new Thread(new RunA(lock));
threadA.start();
threadA.wait();
Thread.sleep(1000);
Thread threadB = new Thread(new RunB(lock));
threadB.start();
}
然后再 run 一下,发现 A end ying 永远不会打印了,咦,为啥 wait(2000)之后没有被唤醒执行下去了呢?
仔细想想 A 显示获得了锁,然后 wait(2000)交出了锁,然后 B 拿到了锁,这个时候过了 2 秒,A 确实是被唤醒了,但很可惜 A 永远也不会得到锁了,对于临界区永远只能有一个线程在执行,不可能出现两个临界区同时在执行代码的可能,所以被唤醒之后,还需要去争抢锁,并不是唤醒了就能继续执行代码的
一个线程被唤醒可能有一下四种情况
-
其它的线程调用 obj.notify(),且当前线程 T,正好是被选中唤醒的。
-
其它的线程调用 obj.notifyAll()。
-
其它线程中断 T。
-
指定的等待时间(timeout)超时,(时间精度会有些误差)。
但我想说一下一个完整的过程是,唤醒之后需要去抢到临界区的锁,才能真正把代码执行下去,光有唤醒是不够的
大多数时候我们忽略唤醒之后需要去抢到临界区的锁,是因为 notify 用的多的关系,触发 notify 的线程必然有锁,只会唤醒一个线程,所以被唤醒的线程必然得到锁!于是大家就会产生一个被唤醒一定能执行点的错觉
而我觉得很多文章没有指明这一点,然当你意识到了这个之后,对 notifyAll 就也不会有一起全部唤醒执行的想当然理解了,notifyAll 只是让大家都去抢临界区,所有的 wait notify,都是为了保护临界区永远只能有一个在执行,分析问题还是从源头入手,见笑。。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于