显然,唤醒等待集中的(任意)一个线程,唤醒等待集中的所有线程。以下讨论应该可以消除任何疑问。 应该大部分时间使用。如果不确定要使用哪个,请使用 。请参阅以下说明。notify
notifyAll
notifyAll
notifyAll
仔细阅读并理解。如果您有任何疑问,请给我发电子邮件。
查看生产者/消费者(假设是具有两种方法的生产者消费者类)。它坏了(因为它使用) - 是的,它可能会工作 - 甚至大多数时候,但它也可能导致死锁 - 我们将看到为什么:notify
public synchronized void put(Object o) {
while (buf.size()==MAX_SIZE) {
wait(); // called if the buffer is full (try/catch removed for brevity)
}
buf.add(o);
notify(); // called in case there are any getters or putters waiting
}
public synchronized Object get() {
// Y: this is where C2 tries to acquire the lock (i.e. at the beginning of the method)
while (buf.size()==0) {
wait(); // called if the buffer is empty (try/catch removed for brevity)
// X: this is where C1 tries to re-acquire the lock (see below)
}
Object o = buf.remove(0);
notify(); // called if there are any getters or putters waiting
return o;
}
首先
为什么我们需要一个围绕等待的一段时间循环?
我们需要一个循环,以防万一我们遇到这种情况:while
使用者 1 (C1) 进入同步块,缓冲区为空,因此 C1 被放入等待集(通过调用)。使用者 2 (C2) 即将进入同步方法(在上面的 Y 点),但生产者 P1 将对象放入缓冲区中,随后调用 。唯一等待的线程是 C1,因此它被唤醒,现在尝试在点 X(上图)重新获取对象锁。wait
notify
现在,C1 和 C2 正在尝试获取同步锁。其中一个(非确定性地)被选中并进入方法,另一个被阻止(不是等待 - 而是被阻止,试图获取方法上的锁定)。假设 C2 首先获得锁定。C1 仍然阻塞(尝试在 X 处获取锁)。C2 完成该方法并释放锁。现在,C1 获取锁。猜猜看,幸运的是我们有一个循环,因为C1执行循环检查(guard),并被阻止从缓冲区中删除不存在的元素(C2已经得到了它!如果我们没有 ,我们将得到一个,因为 C1 试图从缓冲区中删除第一个元素!while
while
IndexArrayOutOfBoundsException
现在
好了,现在我们为什么需要 notifyAll?
在上面的生产者/消费者示例中,看起来我们可以逃脱.看起来是这样,因为我们可以证明生产者和消费者的等待循环中的守卫是相互排斥的。也就是说,看起来我们不能在方法和方法中等待线程,因为要使该线程为真,则以下各项必须为真:notify
put
get
buf.size() == 0 AND buf.size() == MAX_SIZE
(假设MAX_SIZE不是 0)
但是,这还不够好,我们需要使用。让我们看看为什么...notifyAll
假设我们有一个大小为 1 的缓冲区(以使示例易于理解)。以下步骤导致我们陷入僵局。请注意,每当线程被通知唤醒时,JVM可以非确定性地选择它 - 也就是说,任何等待的线程都可以被唤醒。另请注意,当多个线程在进入方法时阻塞(即尝试获取锁)时,获取顺序可能是不确定的。还要记住,一个线程在任何时候都只能位于其中一个方法中 - 同步方法只允许一个线程执行(即持有锁)类中的任何(同步)方法。如果发生以下事件序列 - 将导致死锁:
步骤 1:
- P1 将 1 个字符放入缓冲区
步骤2:
- P2尝试 - 检查等待循环 - 已经是一个字符 - 等待put
步骤3:
- P3尝试 - 检查等待循环 - 已经是一个字符 - 等待put
第4步:
- C1尝试获取1个字符
- C2尝试获取1个字符 - 进入方法
时的块 - C3尝试获得1个字符 - 进入方法时的块get
get
第5步:
- C1正在执行方法 - 获取char,调用,退出方法
- 唤醒P2
- 但是,C2在P2之前进入方法(P2必须重新获取锁),因此P2块在进入方法
时 - C2检查等待循环,缓冲区中没有更多的字符,所以等待
- C3在C2之后进入方法,但在P2之前, 检查等待循环,缓冲区中没有更多字符,因此等待get
notify
notify
put
第6步:
- 现在:有P3,C2和C3等待!
- 最后P2获取锁,在缓冲区中放置一个字符,调用通知,退出方法
第7步:
- P2的通知唤醒P3(记住任何线程都可以被唤醒)
- P3检查等待循环条件,缓冲区中已经有一个字符,所以等待。
- 没有更多的线程调用通知和三个线程永久挂起!
解决方案:替换为生产者/消费者代码(上文)。notify
notifyAll