Java并发编程:等待-通知机制详解与实践
2026/9/15 22:49:47 网站建设 项目流程

1. 并发编程中的等待-通知机制本质

当多个线程需要协同完成某项任务时,单纯的忙等待(busy-waiting)会浪费CPU资源。以生产者-消费者模型为例,如果消费者线程通过循环检查队列是否为空的方式获取数据,会导致CPU占用率居高不下。等待-通知机制通过线程阻塞与唤醒的协作方式解决了这个问题。

Java中的等待-通知机制实现包含三个关键操作:

  • wait():释放锁并进入WAITING状态
  • notify():随机唤醒一个等待线程
  • notifyAll():唤醒所有等待线程

重要提示:这三个方法必须在使用synchronized获取对象锁的代码块内调用,否则会抛出IllegalMonitorStateException

1.1 对象监视器模型

每个Java对象都关联着一个监视器(Monitor),这个监视器维护着:

  1. 一个互斥锁(mutex lock)
  2. 一个等待集合(wait set)
  3. 一个入口集合(entry set)

当线程调用wait()时:

  1. 释放对象锁
  2. 线程状态变为WAITING
  3. 线程进入该对象的等待集合

当其他线程调用notify()时:

  1. JVM从等待集合中随机选择一个线程
  2. 被选中的线程从WAITING状态变为BLOCKED状态
  3. 线程移入入口集合,等待获取锁

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 关键实现要点

  1. 条件检查必须使用while循环

    • 防止虚假唤醒(spurious wakeup)
    • 确保条件真正满足后才继续执行
  2. notify()与notifyAll()的选择

    • notify()效率更高但风险更大
    • notifyAll()更安全但可能引起"惊群效应"
    • 通常建议优先使用notifyAll()
  3. 锁对象的选取原则

    • 使用专门的对象作为锁(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 死锁场景分析

等待-通知机制使用不当可能导致死锁:

  1. 线程A持有锁L1,等待条件C1
  2. 线程B持有锁L2,等待条件C2
  3. C1的满足需要线程B释放L2
  4. C2的满足需要线程A释放L1

排查技巧:使用jstack获取线程转储,检查各线程持有的锁和等待的锁

5. 性能优化实践

5.1 减少锁竞争

  1. 缩小同步代码块范围
  2. 使用读写锁(ReentrantReadWriteLock)替代互斥锁
  3. 考虑使用条件队列(Condition)替代基本等待通知

5.2 避免过早唤醒

// 优化前 public synchronized void process() { notifyAll(); // 不必要地唤醒所有线程 // 长时间处理... } // 优化后 public void process() { // 先完成非同步处理 doLongTimeWork(); synchronized(this) { notifyAll(); // 最后才通知 } }

6. 高级应用场景

6.1 连接池实现示例

class ConnectionPool { private final LinkedList<Connection> 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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询