为什么锁定条件等待必须保持锁定

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

例如,int java monitor lock:

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的定义。


答案 1

想象一下,您有线程可能需要等待的东西。也许你有一个队列,一个线程需要等到队列上有东西,这样它才能处理它。队列必须是线程安全的,因此必须由锁保护。您可以编写以下代码:

  1. 获取锁。
  2. 检查队列是否为空。
  3. 如果队列为空,请等待将某些内容放在队列中。

哎呀,这行不通。我们在队列上保持锁定,那么另一个线程如何在其上放置一些东西?让我们再试一次:

  1. 获取锁。
  2. 检查队列是否为空。
  3. 如果队列为空,请释放锁并等待将某些内容放在队列中。

哎呀,现在我们仍然有问题。如果在我们释放锁之后,但在我们等待将某些东西放在队列上之前,队列上放置了某些东西,该怎么办?在这种情况下,我们将等待已经发生的事情。

条件变量的存在是为了解决这个确切的问题。他们有一个原子的“解锁并等待”操作来关闭此窗口。

因此,await必须保持锁定,否则将无法确保您没有等待已经发生的事情。您必须按住锁以防止另一个线程与您的等待赛跑。


答案 2

那么,我们还在等什么呢?我们正在等待一个条件成为现实。另一个线程将使条件为 true,然后通知等待的线程。

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

因此,在调用之前必须已经获取了锁wait()

synchronized(lock)
{
    if(!condition)
        lock.wait();

如果自动且静默地获取锁定,则许多错误将未被发现。wait()


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

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

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

    while(!condition)
        lock.wait();

但也有一些情况下,我们不想再等了。


有没有合法的用例,裸等待/通知是有意义的?

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

当然;应用程序可以通过裸露的等待/通知来弥补,并具有明确定义的行为;可以认为这是所需的行为;这是该行为的最佳实现。

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


推荐