在 Java 中,java.util.concurrent.locks.Condition
接口提供了类似监视器的方法(await()
, signal()
, signalAll()
)来实现线程间的协调。正确使用 Condition
对象需要遵循特定模式以避免常见问题。
常见问题及修复方案
1. 虚假唤醒问题
问题:线程可能在未收到信号的情况下从 await()
返回(虚假唤醒),如果使用 if
检查条件,可能导致条件不满足时继续执行。
java
lock.lock(); try {if (!condition) { // ❌ 错误:使用if检查条件condition.await();}// 执行操作 } finally {lock.unlock(); }
修复:始终使用 while
循环检查条件:
java
lock.lock(); try {while (!condition) { // ✅ 正确:循环检查条件condition.await();}// 执行操作 } finally {lock.unlock(); }
2. 未持有锁时调用方法
问题:在未持有锁的情况下调用 await()
, signal()
或 signalAll()
会抛出 IllegalMonitorStateException
。
java
Condition condition = lock.newCondition(); condition.await(); // ❌ 错误:未持有锁
修复:确保在持有锁时调用这些方法:
java
lock.lock(); try {condition.await(); // ✅ 正确:持有锁 } finally {lock.unlock(); }
3. 信号丢失问题
问题:在调用 await()
之前调用 signal()
可能导致信号丢失。
java
// 线程A lock.lock(); try {condition.signal(); // ❌ 可能丢失信号 } finally {lock.unlock(); }// 线程B稍后调用await()
修复:确保在调用 await()
之前已经检查条件:
java
lock.lock(); try {while (!condition) {condition.await(); // ✅ 安全等待} } finally {lock.unlock(); }
4. 忘记唤醒等待线程
问题:修改条件后忘记调用 signal()
或 signalAll()
,导致等待线程永远阻塞。
java
lock.lock(); try {condition = true; // 修改条件// 忘记调用 signal() ❌ } finally {lock.unlock(); }
修复:在修改条件后立即发出信号:
java
lock.lock(); try {condition = true;condition.signalAll(); // ✅ 唤醒所有等待线程 } finally {lock.unlock(); }
5. 未处理中断
问题:await()
可能被中断,如果未处理中断,可能导致意外行为。
java
try {condition.await(); // ❌ 未处理中断异常 } catch (InterruptedException e) {// 未处理中断 }
修复:正确处理中断:
java
try {condition.await(); } catch (InterruptedException e) {Thread.currentThread().interrupt(); // ✅ 重新设置中断状态// 处理中断 }
6. 未使用超时导致永久阻塞
问题:如果条件永远不满足,线程可能永久阻塞。
java
condition.await(); // ❌ 可能永久阻塞
修复:使用带超时的 await
方法:
java
if (condition.await(5, TimeUnit.SECONDS)) {// 在超时前收到信号 } else {// 处理超时情况 ✅ }
7. 错误选择 signal() 和 signalAll()
问题:在不合适的场景使用 signal()
而不是 signalAll()
可能遗漏唤醒。
java
condition.signal(); // ❌ 可能只唤醒一个线程,但多个线程在等待
修复:根据需求选择合适的唤醒方法:
java
// 当只需要唤醒一个线程时 condition.signal(); // ✅ 唤醒单个线程// 当需要唤醒所有等待线程时 condition.signalAll(); // ✅ 唤醒所有线程
完整正确示例
java
import java.util.concurrent.locks.*;public class ConditionExample {private final Lock lock = new ReentrantLock();private final Condition condition = lock.newCondition();private boolean resourceReady = false;public void consumer() {lock.lock();try {// 循环检查条件,防止虚假唤醒while (!resourceReady) {try {// 等待并处理中断condition.await();} catch (InterruptedException e) {Thread.currentThread().interrupt();// 处理中断逻辑return;}}// 资源就绪,执行操作System.out.println("Resource consumed");} finally {lock.unlock();}}public void producer() {lock.lock();try {// 准备资源resourceReady = true;// 唤醒所有等待线程condition.signalAll();System.out.println("Resource produced");} finally {lock.unlock();}}public static void main(String[] args) {ConditionExample example = new ConditionExample();// 创建消费者线程Thread consumerThread = new Thread(example::consumer);consumerThread.start();// 稍等片刻try {Thread.sleep(1000);} catch (InterruptedException e) {Thread.currentThread().interrupt();}// 创建生产者线程new Thread(example::producer).start();} }
最佳实践总结
始终使用 while 循环检查条件:防止虚假唤醒
确保持有锁:在调用
await()
,signal()
或signalAll()
前必须持有锁在 finally 块中释放锁:防止死锁
正确处理中断:捕获
InterruptedException
并重新设置中断状态使用超时机制:避免永久阻塞
明确信号选择:
signal()
:当只需唤醒一个线程时使用signalAll()
:当需要唤醒所有等待线程时使用
条件变量与谓词关联:每个条件变量应该关联一个明确的条件谓词
遵循这些模式可以避免使用 Condition
时的常见陷阱,确保线程安全协调。