为什么我们需要notify()来进行线程间通信



从对象notify()的JAVA文档

唤醒的线程将无法继续,直到当前线程放弃对此对象的锁定。

这意味着,除非通知的线程,其同步块完成并释放锁,否则等待的线程无法继续。如果是这种情况,那么如果同步块无论如何都要执行,那么使用notify()有什么意义?如果notify()不唤醒等待线程并让它完成自己的工作,那么它的实际用途是什么?

好问题。将引导您查看线程状态类。

调用Object.notify方法的线程使以前调用Object.wait的线程现在能够由线程调度器调度。换句话说,等待的线程现在是"runnable"。虽然它是"可运行的",但它不是"正在运行的"。

只有当调用notify的线程释放锁时,它才能继续运行——一种方法是当它退出同步块时。

网上有很多关于线程状态的示意图。其中一些是完全不正确或令人困惑的,因为它们引入了官方文件中没有的术语。这是一个对我来说有意义的。

严格来说,我们不会:我们可以让等待的线程运行一个循环,在那里它重新获取锁,检查条件,并睡眠一小段时间。但是使用wait()notify()要高效得多,因为这样等待的线程就不会一直唤醒并占用CPU(并占用锁)。

notify()notifyAll()用于唤醒在调用notify()notifyAll()的同一对象上调用wait()的线程。如果不调用notify(),那些"等待"的线程将永远等待(尽管JVM规范说线程有时可能会在没有调用通知的情况下醒来)。另外,因为对notify()的调用不会释放与对象本身相关的锁,所以该调用通常是synchronized块中的最后一条语句。

因此CCD_ 18与CCD_ 19一起使用,而不是单独使用。通常情况下,用例如下(阻塞大小有限的队列)。

将元素添加到队列(一些伪代码)的方法

synchronized(lockObject) {
if (size < LIMIT) {
addElement();
lockObject.notifyAll(); //notifying threads that are waiting to get element from empty queue
} else {
lockObject.wait(); // waiting for other thread to get element from queue and make room for new element
}
}

获取元素的方法

synchronized(lockObject) {
if (size > 0) {
getElement();
lockObject.notifyAll(); // notify threads that there is a room for new element
} else {
lockObject.wait(); // waiting for other thread to put element into the queue
}
} 

同时调用lockObject.wait()释放对lockObject的锁定。关于这方面的更多细节可以在这里找到:Java:Do wait()从同步块释放锁

通知是唤醒正在等待的线程的方法。如果您删除了notify,那么等待线程将继续等待(除非出现虚假唤醒,但我们现在不去那里)。

(中断会唤醒线程,但指导是仅将其用于取消。中断针对特定线程,其中通知可以让调度程序决定哪些线程受到影响。)

当线程调用wait时,它必须拥有锁,然后wait方法释放锁。

当线程调用notify时,它必须拥有锁。

实际上,在通知线程放弃锁之前,通知不能在任何等待线程上生效。无论如何,被通知的线程需要做的第一件事就是尝试获取锁。你引用的所有段落都试图说,当线程调用notify时,唤醒不会立即发生。

因此,这里发生的情况是,通知线程释放锁并将通知发送给调度程序,调度程序决定通知哪个线程,然后被通知的线程醒来并争夺锁,以便离开等待方法。

想象一下,如果你需要一个线程等待另一个线程来做一些它可能正在或可能不在当前活动处理的事情。例如,一个正在等待作业的线程可能需要等待,直到另一个程序将作业放在它应该做的作业列表中,如果该列表为空。你会怎么做?

你不能只是使用某种形式的相互排斥。可能会有很长一段时间没有工作要做,并且线程没有在队列上持有任何锁。现在可能没有任何工作要做。工作的线程需要等待,而不持有任何锁,直到另一个线程给它一些工作要做

所以在某个地方,有一个线程可以做这样的事情:

  1. 获取锁,该锁保护另一个线程可能正在等待更改的共享状态。(在这种情况下,是作业队列。)
  2. 更改共享状态以反映线程可能需要等待的事情已经发生的事实。(也就是说,将作业放入队列。)
  3. 释放锁并让任何等待的线程知道事情已经发生

那么我们等待的代码会是什么样子呢?也许:

  1. 获取保护共享状态的锁
  2. 检查我们是否需要等待。(队列中有作业吗?)
  3. 如果我们需要等待,请等待。(如果没有,请等待将作业放入队列。)

哎呀,我们有个问题。我们正在等待的事情不可能发生,因为我们持有锁。没有其他线程可以更改共享状态。(在我们释放步骤1中获得的锁之前,我们将作业放入队列的线程无法接触队列。)

让我们再试一次:

  1. 获取保护共享状态的锁
  2. 检查我们是否需要等待。(队列中有作业吗?)
  3. 如果我们不需要等待,请退出此算法。(如果有作业,请将其从队列中删除,释放锁,然后执行。)
  4. 松开锁。(因此,另一个线程可以将作业放入队列。)
  5. 等待事情发生

哦,我们有另一个问题。如果我们正在等待的事情发生在第4步之后,但发生在第5步之前,该怎么办。既然锁已经解除,我们等待的事情就有可能发生。我们不能再检查了,因为我们没有锁。我们如何确保我们不会等待已经发生的事情,这可能意味着永远等待?

为了解决这个问题,我们需要一个原子"解锁并等待"操作。wait就是这么做的。我们还需要一些可以结束等待的操作,这些操作可以由更改共享状态的线程调用,这样我们就不再需要等待了。notify就是这么做的。

最新更新