为什么 Lock condition await 必须持有锁

Why Lock condition await must hold the lock

我对此表示怀疑,在 Java 语言中,我们需要先获取锁,然后再等待某些条件得到满足。

例如int java监控锁:

synchronized(lock){
   System.out.println("before lock ...");
   lock.wait();
   System.out.println("after lock ...");
}

或并发实用程序:

Lock lock = new ReentrantLock();
Condition cond = lock.newCondition();

lock.lock();
try{
     System.out.println("before condition ...");
     cond.await();
     System.out.println("after condition ...");
}catch(Exception e){
     e.printStackTrace();
}finally{
     lock.unlock();
}

那么,为什么我们不能在没有持有锁的情况下等待?

其他语言是否有所不同,或者只是 Java?

希望设计后能说明原因,但不只是针对JAVA-SPEC定义。

简单的答案是因为否则你会得到 IllegalMonitorStateException,它在 Object.wait javadoc 中指定。在内部,Java 中的同步使用底层 OS 机制。所以不只是Java.

请参阅 Condition 的文档。

Condition 就像一个对象的等待池或等待集,它取代了对象监控方法(wait、notify 和 notifyAll)的使用。条件允许一个线程暂停执行(到 "wait"),直到另一个线程通知某个状态条件现在可能为真。 Condition 实例本质上绑定到一个锁,就像对象监视器方法需要共享对象的锁来等待或通知一样。因此,在根据条件调用 await() 之前,线程必须锁定用于生成条件的 Lock 对象。当调用 await() 方法时,与条件关联的锁被释放。

如果线程只是在等待信号继续进行,那么还有其他机制可以做到这一点。大概有一些状态受锁保护,线程正在等待操作并满足某些条件。为了正确保护该状态,线程在等待条件之前和之后都应该拥有锁,因此需要获取锁是有意义的。

好吧,我们还在等什么?我们正在等待条件变为真。另一个线程将使条件为真,然后通知等待线程。

在进入等待之前,我们必须检查条件是否为假;这个检查和等待必须是 atomic,即在同一个锁下。否则,如果我们在条件已经为真时进入等待,我们可能永远不会醒来。

因此需要在调用wait()

之前已经获取锁
synchronized(lock)
{
    if(!condition)
        lock.wait();

如果 wait() 自动且无提示地获取锁,很多 错误将不会被发现。


wait() 唤醒后,我们必须再次检查条件 -- 不能保证这里的条件一定变为真(有很多原因 - 虚假唤醒;超时、中断、多个等待者、多个条件)

synchronized(lock)
{
    if(!condition)
        lock.wait();
    if(!condition)   // check again
        ...

通常情况下,如果条件仍然为假,我们将再次等待。因此典型的模式是

    while(!condition)
        lock.wait();

但也有不想再等的情况


是否存在裸 wait/notify 有意义的合法用例?

synchronized(lock){ lock.wait(); }

当然;应用程序可以由裸 wait/notify 组成,具有明确的行为;可以说这是期望的行为;这是该行为的最佳实现。

但是,这不是典型的使用模式,没有理由在 API 设计中考虑它。

假设您有一个线程可能需要等待的东西。也许你有一个队列,一个线程需要等到队列中有东西才能处理它。队列必须是线程安全的,所以它必须用锁来保护。您可能会编写以下代码:

  1. 获取锁。
  2. 检查队列是否为空。
  3. 如果队列为空,等待队列中的东西。

糟糕,那行不通了。我们持有队列上的锁,那么另一个线程怎么能在上面放东西呢?我们再试一次:

  1. 获取锁。
  2. 检查队列是否为空。
  3. 如果队列为空,则释放锁并等待队列中的东西。

糟糕,现在我们还有问题。如果在我们释放锁之后但在我们等待将某些东西放入队列之前,有东西被放入队列怎么办?那样的话,我们将等待已经发生的事情。

存在条件变量来解决这个确切的问题。他们有一个原子 "unlock and wait" 操作来关闭这个 window.

所以 await 必须持有锁,否则无法确保您没有在等待已经发生的事情。您必须持有锁以防止另一个线程与您的等待竞争。

听起来合理的答案

这是一个 JVM 的东西。对象 x 具有:

  • an Entry Set:尝试 synchronized(x)

  • 的线程队列
  • a Waiting Set:名为 x.wait()

  • 的线程队列

当你调用x.wait()时,JVM将你当前的线程添加到Waiting Set;当您调用 x.notify()/x.notifyAll() 时,JVM 会从 Waiting Set.

中删除 one/all 元素

多个线程可以调用x.wait()/x.notify()/x.notifyAll()来修改Waiting Set。为了保证Waiting Set线程安全,JVM一次只接受一个线程的一个操作。