不,Object::wait
不会被调用。wait
/机制是在notify
提供的基本锁定之上的附加层synchronized
;可以在synchronized
不使用wait
or的情况下使用notify
。
基本synchronized
机制基于锁定和解锁附加到特定对象的锁(有时称为监视器)的想法。如果一个线程锁定了锁,那么另一个试图锁定它的线程将阻塞。当第一个线程解锁锁时,第二个线程解除阻塞,并继续锁定锁。
wait
/notify
机制为线程提供了一种暂时放弃它持有的锁的方法,同时重新获取锁由其他持有该锁的线程控制。考虑这段代码:
public synchronized void first() {
System.out.println("first before");
wait();
System.out.println("first after");
}
public synchronized void second() {
System.out.println("second before");
notify();
System.out.println("second after");
}
说一个线程,线程 A,调用first
,然后另一个线程 B,调用second
。事件的顺序是:
- A 尝试获取锁
- A成功获取锁
- A 将“first before”写入 System.out
- B 尝试获取锁
- B 无法获取锁,因为 A 有它,所以它阻塞
- A 完成写入并调用
wait
- 此时 A释放锁并开始等待
- B 现在成功获取锁,并解除阻塞
- B 将“第二个之前”写入 System.out
- B 完成写入并调用
notify
- 这对 B 没有影响,但这意味着 A停止等待并尝试重新获取锁
- A无法获取锁,因为B有,所以阻塞
- B 向 System.out 写入“秒后”
- B 完成方法,并释放锁
- A 现在成功获取锁,并解除阻塞
- A 继续往 System.out 写入“first after”
- A 完成方法,并释放锁
这是一个冗长的描述,但它确实是一个非常简单的过程 - wait
/notify
调用有点让第一个线程将锁借给另一个线程使用。
重要的是要意识到有两种不同类型的阻塞正在发生。首先,线程在进入一个块(或从调用synchronized
返回时重新进入一个块)时阻塞等待获取锁的方式。wait
其次,线程在调用wait
之后阻塞的方式,在被相应的notify
.
我已经将wait
/描述notify
为一个线程借给另一个线程。这就是我的想法,我认为这是一个富有成效的比喻。用一个令人毛骨悚然的比喻来说,也许就像一个吸血鬼搬进城堡,然后睡在棺材里。一旦他睡着了,一些无辜的游客就会进来把城堡出租作为度假屋。在某个时候,游客探索了地下室并打扰了棺材,此时吸血鬼醒来并想要回到他的城堡。一旦游客惊恐地逃走,他就可以搬回房子里。
之所以有wait
andnotify
的名字,而不是 and 之类的名字lend
,return
是因为它们通常用于构建线程间通信机制,这里强调的不是第一个线程最初借出锁,而是唤醒由第二个线程的服务员。
现在,最后转向你的第二个问题,有两件事需要考虑。
第一个是“虚假唤醒”的可能性——参见第17.2.1 节嵌套项目符号列表深处的小注释。等待Java 语言规范:
由于 [...] 实现的内部操作,线程可能会从等待集中删除。尽管不鼓励实现,但允许执行“虚假唤醒”,即从等待集中删除线程,从而在没有明确指令的情况下启用恢复。
即线程通常只会在收到通知时才唤醒,但也有可能在没有收到通知时随机唤醒。因此,您确实需要wait
使用涉及检查条件变量的循环来保护 a ,就像在您的示例中一样。正如规范所说:
请注意,此规定需要 Java 编码实践,即仅在循环中使用等待,该循环仅在线程等待的某些逻辑条件成立时终止。
第二个是中断。中断不是随机的;只有当其他线程调用interrupt
了正在等待的线程时,才会发生中断。发生这种情况时,它将立即停止阻塞,并InterruptedException
退出wait
调用。与您所看到的相反,捕获此异常并再次等待是不正确的。原因很简单:如果有人interrupt
点了你的帖子,正是因为他们想让你stop waiting
!不可能确切地说出线程应该做什么,但通常的方法是中止当前工作,并将控制权返回给调用者。如果在当前工作中止后调用者无法继续,那么它也应该中止,依此类推,直到达到可以做一些有意义的事情的级别。正确处理中断是一个太大的主题,无法在这里处理,但首先要阅读教程中关于Supporting Interruption的内容,如果可能,请阅读Java Concurrency In Practice。