为什么 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 设计中考虑它。
假设您有一个线程可能需要等待的东西。也许你有一个队列,一个线程需要等到队列中有东西才能处理它。队列必须是线程安全的,所以它必须用锁来保护。您可能会编写以下代码:
- 获取锁。
- 检查队列是否为空。
- 如果队列为空,等待队列中的东西。
糟糕,那行不通了。我们持有队列上的锁,那么另一个线程怎么能在上面放东西呢?我们再试一次:
- 获取锁。
- 检查队列是否为空。
- 如果队列为空,则释放锁并等待队列中的东西。
糟糕,现在我们还有问题。如果在我们释放锁之后但在我们等待将某些东西放入队列之前,有东西被放入队列怎么办?那样的话,我们将等待已经发生的事情。
存在条件变量来解决这个确切的问题。他们有一个原子 "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一次只接受一个线程的一个操作。
我对此表示怀疑,在 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 设计中考虑它。
假设您有一个线程可能需要等待的东西。也许你有一个队列,一个线程需要等到队列中有东西才能处理它。队列必须是线程安全的,所以它必须用锁来保护。您可能会编写以下代码:
- 获取锁。
- 检查队列是否为空。
- 如果队列为空,等待队列中的东西。
糟糕,那行不通了。我们持有队列上的锁,那么另一个线程怎么能在上面放东西呢?我们再试一次:
- 获取锁。
- 检查队列是否为空。
- 如果队列为空,则释放锁并等待队列中的东西。
糟糕,现在我们还有问题。如果在我们释放锁之后但在我们等待将某些东西放入队列之前,有东西被放入队列怎么办?那样的话,我们将等待已经发生的事情。
存在条件变量来解决这个确切的问题。他们有一个原子 "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
.
多个线程可以调用x.wait()
/x.notify()
/x.notifyAll()
来修改Waiting Set
。为了保证Waiting Set
线程安全,JVM一次只接受一个线程的一个操作。