3. notify vs notifyAll

在 Java 多线程编程中,notify()notifyAll() 是用于线程间通信的关键方法,它们与 wait() 方法配合使用,协调多个线程对共享资源的访问。以下是它们的区别、应用场景及底层机制的详细解析:


1. 核心区别

方法行为适用场景
notify()随机唤醒一个正在该对象上等待的线程(由 JVM 决定唤醒哪一个)。所有等待线程的唤醒条件相同,且只需一个线程处理任务(如单资源释放)。
notifyAll()唤醒所有正在该对象上等待的线程,线程需重新竞争锁。等待线程的唤醒条件不同,或需要多个线程协作处理(如多条件判断、资源批量分配)。

2. 底层机制

  • 锁竞争
    • notify() 仅唤醒一个线程,减少了锁竞争和上下文切换开销。
    • notifyAll() 唤醒所有线程,但最终只有一个线程能获得锁,其他线程会重新阻塞,可能导致性能损耗。
  • 条件队列
    • 每个对象的监视器锁(Monitor)关联一个等待队列(存放调用 wait() 的线程)。
    • notify() 将队列中的一个线程移到锁的入口队列(Entry Set)。
    • notifyAll() 将所有等待线程移到入口队列,重新竞争锁。

3. 经典应用场景

场景 1:单资源释放(notify() 更高效)

问题:多个线程等待使用唯一资源(如数据库连接池中的一个连接)。
解决:释放资源后只需唤醒一个等待线程。

class ConnectionPool {
    private final Object lock = new Object();
    private boolean isAvailable = true;

    public void useConnection() {
        synchronized (lock) {
            while (!isAvailable) {
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
            isAvailable = false;
            // 使用连接...
        }
    }

    public void releaseConnection() {
        synchronized (lock) {
            isAvailable = true;
            lock.notify(); // 唤醒一个等待线程
        }
    }
}
场景 2:多条件判断(必须用 notifyAll()

问题:线程等待不同条件(如生产者-消费者模型中,缓冲区满时唤醒消费者,空时唤醒生产者)。
解决:唤醒所有线程,确保正确类型的线程能响应条件变化。

class BoundedBuffer {
    private final Object lock = new Object();
    private final Queue<Integer> buffer = new LinkedList<>();
    private final int CAPACITY = 10;

    public void produce(int value) throws InterruptedException {
        synchronized (lock) {
            while (buffer.size() == CAPACITY) {
                lock.wait(); // 缓冲区满,生产者等待
            }
            buffer.add(value);
            lock.notifyAll(); // 唤醒所有(可能有消费者在等待)
        }
    }

    public int consume() throws InterruptedException {
        synchronized (lock) {
            while (buffer.isEmpty()) {
                lock.wait(); // 缓冲区空,消费者等待
            }
            int value = buffer.poll();
            lock.notifyAll(); // 唤醒所有(可能有生产者在等待)
            return value;
        }
    }
}

4. 常见陷阱与解决方案

陷阱 1:错误使用 notify() 导致线程饥饿
  • 现象:某些线程因未被唤醒而永久等待。
  • 解决:确保所有相关条件变化时使用 notifyAll(),或明确知道仅需唤醒一个线程。
陷阱 2:虚假唤醒(Spurious Wakeup)
  • 现象:线程未收到通知却从 wait() 返回(底层 OS 机制导致)。
  • 解决:始终在循环中检查条件,而非 if 语句:
    synchronized (lock) {
        while (!condition) { // 必须用 while,不能用 if
            lock.wait();
        }
        // 处理任务
    }
    

5. 性能考量

场景notify()notifyAll()
竞争程度低(仅一个线程被唤醒)高(所有线程竞争锁)
上下文切换较少较多(但最终只有一个线程运行)
适用性单条件、单一任务类型多条件、复杂协作场景

6. 最佳实践

  1. 默认使用 notifyAll()
    除非明确只需唤醒一个线程,否则优先使用 notifyAll(),避免因条件遗漏导致线程饥饿。
  2. 封装条件检查
    将条件判断和等待逻辑封装到方法中,确保代码可维护性。
  3. 结合 java.util.concurrent 工具
    使用 LockCondition 等高级 API(替代 synchronized + wait/notify),提供更精细的控制:
    Lock lock = new ReentrantLock();
    Condition notEmpty = lock.newCondition();
    Condition notFull = lock.newCondition();
    
    public void produce(int value) {
        lock.lock();
        try {
            while (buffer.size() == CAPACITY) {
                notFull.await(); // 等待非满条件
            }
            buffer.add(value);
            notEmpty.signalAll(); // 唤醒消费者
        } finally {
            lock.unlock();
        }
    }
    

总结

  • notify():轻量级唤醒,适合单一条件、单任务场景,需确保不会遗漏任何必要唤醒。
  • notifyAll():安全唤醒所有线程,适合多条件协作或不确定唤醒哪个线程的场景,但可能增加竞争开销。
  • 核心原则
    • 使用 while 循环检查条件,避免虚假唤醒。
    • 在复杂条件或多类型等待任务中,优先选择 notifyAll()Condition 对象。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值