线程通信(wait、notify)与虚假唤醒

wait()notify() 介绍

wait()notify() 是 Java 中用于线程间通信的基本机制,定义在 Object 类中(所有 Java 对象的基类),必须配合 synchronized 使用。

wait()

  • 让当前线程释放锁并进入等待状态(WAITING)
  • 直到其他线程调用 notify()/notifyAll() 或线程被中断
  • 必须在同步代码块中调用(持有对象锁时)

notify()

  • 随机唤醒一个在该对象上等待的线程
  • 被唤醒的线程会尝试重新获取锁
  • 必须在同步代码块中调用(持有对象锁时)

notifyAll()

  • 唤醒所有在该对象上等待的线程
  • 唤醒后的线程会竞争获取锁
  • 必须在同步代码块中调用(持有对象锁时)

生产者-消费者示例

import java.util.LinkedList;
import java.util.Queue;

public class ProducerConsumerExample {
    private final Queue<Integer> queue = new LinkedList<>();
    private final int MAX_SIZE = 10;
    private final Object lock = new Object();

    // 生产者
    public void produce() throws InterruptedException {
        int value = 0;
        while (true) {
            synchronized (lock) {
                // 使用while而不是if防止虚假唤醒
                while (queue.size() == MAX_SIZE) {
                    lock.wait();
                }
                queue.offer(value++);
                System.out.println("Produced: " + value + ", Queue size: " + queue.size());
                lock.notifyAll(); // 唤醒所有等待线程
                Thread.sleep(500); // 模拟生产耗时
            }
        }
    }

    // 消费者
    public void consume() throws InterruptedException {
        while (true) {
            synchronized (lock) {
                // 使用while而不是if防止虚假唤醒
                while (queue.isEmpty()) {
                    lock.wait();
                }
                int value = queue.poll();
                System.out.println("Consumed: " + value + ", Queue size: " + queue.size());
                lock.notifyAll(); // 唤醒所有等待线程
                Thread.sleep(1000); // 模拟消费耗时
            }
        }
    }

    public static void main(String[] args) {
        ProducerConsumerExample example = new ProducerConsumerExample();
        
        Thread producer = new Thread(() -> {
            try {
                example.produce();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        
        Thread consumer = new Thread(() -> {
            try {
                example.consume();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        
        producer.start();
        consumer.start();
    }
}

虚假唤醒(Spurious Wakeup)

什么是虚假唤醒?

  • 线程在没有收到 notify()/notifyAll() 或中断的情况下从 wait() 中醒来
  • 虽然不常见,但必须防范

如何防范?

  • 总是使用 while 循环检查条件,而不是 if 语句

    synchronized (lock) {
        while (conditionNotMet) {  // 不是 if (conditionNotMet)
            lock.wait();
        }
        // 执行操作
    }
    
  • 这样即使发生虚假唤醒,线程会再次检查条件并可能重新等待

底层原因:

  • 与操作系统线程调度机制有关
  • Java 规范明确允许这种行为(JLS 17.2.1)
  • 设计上是为了在某些情况下提高性能
posted @ 2024-08-01 13:35  CyrusHuang  阅读(15)  评论(0)    收藏  举报