为何Java的ReentrantLock要求调用Condition方法前必须持有锁?
保证条件检查与状态操作的原子性
Condition是配合锁实现「等待-通知」模式的工具,正常使用的范式是先加锁,再检查条件,不满足就等待,满足后执行操作再通知其他线程。如果不强制持有锁,线程在检查条件和调用await/signal的间隙,可能被其他线程修改条件状态,比如线程A刚判断条件不满足,还没调用await,线程B就修改了条件并发出通知,这时候A再await就会错过通知,陷入永久等待,直接破坏业务逻辑的正确性。对齐Java内置监视器的语义习惯
Java里用synchronized配合wait()/notify()时,同样要求调用线程持有对象锁。ReentrantLock作为显式锁,Condition的设计延续了这一规则,让开发者不用切换思维模式,降低学习成本和误用概率。避免Condition等待队列的并发混乱
Condition内部维护着等待线程的队列,如果允许无锁调用signal/await,多个线程同时操作这个队列会引发竞态条件——比如节点的添加、移除操作可能出现不一致,导致线程管理混乱,甚至触发死锁。持有锁能保证对Condition队列的操作是串行化的,从机制上避免这类问题。
对比C++的std::condition_variable,它确实允许无锁调用通知方法,但这要求开发者手动用锁保护条件状态的检查和修改,本质是把锁的持有要求从API层面转移到了编码规范上。Java选择在API层面强制锁持有,是为了避免开发者因疏忽遗漏锁的使用,从根源上减少并发bug。
实际测试中,未持有锁调用Condition方法会抛出明确异常:
java.lang.IllegalMonitorStateException at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.signalAll(AbstractQueuedSynchronizer.java:1954) at PingPongPrinter_.printPing(PingPong2.java:35) at WorkerPing.run(PingPong2.java:53) at java.lang.Thread.run(Thread.java:748)
内容的提问来源于stack exchange,提问作者Suvam Roy

