为什么Lock条件等待必须持有锁

Chi*_*ing 19 java concurrency multithreading wait java.util.concurrent

我对此表示怀疑,在Java语言中,我们需要保持锁定,然后再等待满足某些条件.

例如,int java monitor lock:

synchronized(lock){
   System.out.println("before lock ...");
   lock.wait();
   System.out.println("after lock ...");
}
Run Code Online (Sandbox Code Playgroud)

或者是黄色的工具.

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();
}
Run Code Online (Sandbox Code Playgroud)

所以,为什么我们不能等待,而不是锁定?

如果只是因为Java,其他语言的工作方式不同?

我希望你能在设计之后解释原因,但不仅仅是JAVA-SPEC的定义.

Dav*_*rtz 14

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

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

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

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

哎呀,现在我们还有问题.如果在我们释放锁之后但在等待将某些东西放在队列之前,会有什么东西放在队列中呢?在那种情况下,我们将等待已经发生的事情.

存在条件变量来解决这个确切的问题.他们有一个关闭此窗口的原子"解锁和等待"操作.

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


Zho*_*gYu 8

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

在进入等待之前,我们必须检查条件是否为假; 这个检查和等待必须是原子的,即在同一个锁下.否则,如果我们在条件已经为真的情况下进入等待,我们可能永远不会被唤醒.

因此,必须在呼叫之前获取锁定wait()

synchronized(lock)
{
    if(!condition)
        lock.wait();
Run Code Online (Sandbox Code Playgroud)

如果wait()自动且无声地获取锁定,则很多错误将不会被检测到.


一旦被唤醒wait(),我们必须再次检查条件 - 不能保证条件必须在这里变为真(由于很多原因 - 虚假唤醒;超时,中断,多个服务员,多个条件)

synchronized(lock)
{
    if(!condition)
        lock.wait();
    if(!condition)   // check again
        ...
Run Code Online (Sandbox Code Playgroud)

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

    while(!condition)
        lock.wait();
Run Code Online (Sandbox Code Playgroud)

但也有一些我们不想再等的情况.


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

synchronized(lock){ lock.wait(); }
Run Code Online (Sandbox Code Playgroud)

当然; 一个应用程序可以用裸等待/通知组成,具有明确定义的行为; 可以认为这是理想的行为; 这是该行为的最佳实现.

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