ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Java并发编程:等待-通知机制详解与实践

Java并发编程:等待-通知机制详解与实践 1. 并发编程中的等待-通知机制本质当多个线程需要协同完成某项任务时单纯的忙等待busy-waiting会浪费CPU资源。以生产者-消费者模型为例如果消费者线程通过循环检查队列是否为空的方式获取数据会导致CPU占用率居高不下。等待-通知机制通过线程阻塞与唤醒的协作方式解决了这个问题。Java中的等待-通知机制实现包含三个关键操作wait()释放锁并进入WAITING状态notify()随机唤醒一个等待线程notifyAll()唤醒所有等待线程重要提示这三个方法必须在使用synchronized获取对象锁的代码块内调用否则会抛出IllegalMonitorStateException1.1 对象监视器模型每个Java对象都关联着一个监视器Monitor这个监视器维护着一个互斥锁mutex lock一个等待集合wait set一个入口集合entry set当线程调用wait()时释放对象锁线程状态变为WAITING线程进入该对象的等待集合当其他线程调用notify()时JVM从等待集合中随机选择一个线程被选中的线程从WAITING状态变为BLOCKED状态线程移入入口集合等待获取锁2. 标准等待-通知范式实现2.1 基础模板代码// 共享对象 class SharedObject { // 条件谓词 private boolean condition false; public synchronized void waitForCondition() throws InterruptedException { // 必须使用循环检查条件 while (!condition) { wait(); } // 条件满足后的处理逻辑 doSomething(); } public synchronized void changeCondition() { condition true; notifyAll(); // 或 notify() } }2.2 关键实现要点条件检查必须使用while循环防止虚假唤醒spurious wakeup确保条件真正满足后才继续执行notify()与notifyAll()的选择notify()效率更高但风险更大notifyAll()更安全但可能引起惊群效应通常建议优先使用notifyAll()锁对象的选取原则使用专门的对象作为锁private final Object避免使用业务对象或this作为锁3. 生产级应用实践3.1 带超时的等待实现public synchronized boolean waitForCondition(long timeout) throws InterruptedException { long endTime System.currentTimeMillis() timeout; while (!condition) { long remaining endTime - System.currentTimeMillis(); if (remaining 0) { return false; // 超时未满足 } wait(remaining); } return true; }3.2 多条件谓词处理class MultiCondition { private final Object lock new Object(); private boolean conditionA false; private boolean conditionB false; public void waitForA() throws InterruptedException { synchronized (lock) { while (!conditionA) { lock.wait(); } } } public void signalA() { synchronized (lock) { conditionA true; lock.notifyAll(); } } // 类似实现conditionB... }4. 典型问题排查指南4.1 常见问题现象问题现象可能原因解决方案IllegalMonitorStateException未获取锁就调用wait/notify确保在synchronized块内调用线程永久阻塞忘记调用notify检查所有条件变更路径虚假唤醒使用if检查条件改用while循环检查性能问题过度使用notifyAll评估是否可用notify替代4.2 死锁场景分析等待-通知机制使用不当可能导致死锁线程A持有锁L1等待条件C1线程B持有锁L2等待条件C2C1的满足需要线程B释放L2C2的满足需要线程A释放L1排查技巧使用jstack获取线程转储检查各线程持有的锁和等待的锁5. 性能优化实践5.1 减少锁竞争缩小同步代码块范围使用读写锁ReentrantReadWriteLock替代互斥锁考虑使用条件队列Condition替代基本等待通知5.2 避免过早唤醒// 优化前 public synchronized void process() { notifyAll(); // 不必要地唤醒所有线程 // 长时间处理... } // 优化后 public void process() { // 先完成非同步处理 doLongTimeWork(); synchronized(this) { notifyAll(); // 最后才通知 } }6. 高级应用场景6.1 连接池实现示例class ConnectionPool { private final LinkedListConnection pool new LinkedList(); private final int maxSize; public ConnectionPool(int maxSize) { this.maxSize maxSize; } public Connection get() throws InterruptedException { synchronized (pool) { while (pool.isEmpty()) { pool.wait(); } return pool.removeFirst(); } } public void release(Connection conn) { synchronized (pool) { pool.addLast(conn); pool.notify(); // 只需唤醒一个等待线程 } } }6.2 批量任务处理class BatchProcessor { private int count 0; private final int batchSize; public BatchProcessor(int batchSize) { this.batchSize batchSize; } public synchronized void await() throws InterruptedException { count; if (count batchSize) { wait(); } else { count 0; notifyAll(); // 唤醒所有等待线程 } } }在实际项目中等待-通知机制的性能表现会受JVM实现影响。以Oracle HotSpot JVM为例notify()的平均唤醒延迟通常在微秒级别但在高并发场景下可能上升到毫秒级。对于超低延迟要求的系统可以考虑使用Java 9引入的VarHandle或sun.misc.Unsafe不推荐提供的更底层API
返回列表