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

时间:2015-09-25 03:38:35

标签: java multithreading concurrency wait java.util.concurrent

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

例如,int java monitor lock:

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

或来自cucurrent utils。

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

6 个答案:

答案 0 :(得分:12)

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

  1. 获取锁。
  2. 检查队列是否为空。
  3. 如果队列为空,请等待将某些内容放入队列。
  4. 哎呀,这不会奏效。我们保持对队列的锁定,那么另一个线程如何在其上放置一些东西?让我们再试一次:

    1. 获取锁。
    2. 检查队列是否为空。
    3. 如果队列为空,请释放锁并等待将某些内容放在队列中。
    4. 哎呀,现在我们还有问题。如果在我们释放锁之后但在等待将某些东西放在队列之前,会有什么东西放在队列中呢?在这种情况下,我们将等待已经发生的事情。

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

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

答案 1 :(得分:8)

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

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

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

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

如果wait()自动且无声地获取锁定,则批次的错误将无法检测到。

wait()唤醒后,我们必须再次检查条件 - 无法保证条件必须在此处变为真(因为很多原因 - 虚假唤醒;超时,中断,多个服务员,多个条件)< / p>

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

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

    while(!condition)
        lock.wait();

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

裸露的等待/通知是否有合理的用例?

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

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

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

答案 2 :(得分:1)

请参阅Condition的文档。

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

答案 3 :(得分:1)

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

答案 4 :(得分:0)

合理的答案

这是JVM的事情。对象x具有:

  • 一个Entry Set:尝试synchronized(x)

  • 的线程的队列
  • 一个Waiting Set:称为x.wait()

  • 的线程队列

调用x.wait()时,JVM将当前线程添加到Waiting Set中;当您调用x.notify() / x.notifyAll()时,JVM从Waiting Set中删除一个/所有元素。

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

答案 5 :(得分:-1)

简单的答案是因为否则您将获得在Object.wait javadoc中指定的IllegalMonitorStateException。在内部,Java中的同步使用底层操作系统机制。所以它不仅仅是Java。