← 返回首页

Java多线程之间的通信原理

Java 多线程通信原理

Java 中多线程之间本质上是独立执行的,线程通信的核心问题是:一个线程如何通知另一个线程,或者多个线程如何协调执行顺序、共享状态。

Java 提供了多种线程通信机制,从底层到高层可以分为以下几类:


基于 wait() / notify() 的通信(Object 监视器机制)

这是 Java 最基础的线程通信方式,定义在 Object 类中,所有对象都可以使用。

  • wait():当前线程释放对象锁并进入等待状态,直到其他线程调用该对象的 notify()notifyAll()
  • notify():随机唤醒一个在该对象上等待的线程。
  • notifyAll():唤醒所有在该对象上等待的线程。
class SharedResource {
    private boolean ready = false;
public synchronized void produce() throws InterruptedException {
    while (ready) {
        wait(); // 已经生产过了,等待消费
    }
    System.out.println("生产完成");
    ready = true;
    notify(); // 通知消费者
}

public synchronized void consume() throws InterruptedException {
    while (!ready) {
        wait(); // 还没生产,等待
    }
    System.out.println("消费完成");
    ready = false;
    notify(); // 通知生产者
}

}

原理:每个 Java 对象内部都有一个 Monitor(监视器锁)wait() 会将线程放入该对象的 等待队列(WaitSet) 中并释放锁;notify() 从等待队列中取出一个线程,使其重新竞争锁。

注意:wait() 必须放在 while 循环中而非 if 中,以防止虚假唤醒(Spurious Wakeup)


基于 volatile 的通信

volatile 关键字保证变量的可见性——一个线程修改了 volatile 变量,其他线程能立即看到最新值。

class Task {
    volatile boolean running = true;
public void stop() {
    running = false;
}

public void run() {
    while (running) {
        // 持续工作
    }
    System.out.println("线程停止");
}

}

原理volatile 通过内存屏障(Memory Barrier)禁止 CPU 缓存优化,强制线程每次都从主内存中读取变量值,而非工作内存中的副本。

注意:volatile 只保证可见性和有序性,不保证原子性,不适合用于复合操作(如 i++)。


基于 Lock + Condition 的通信

JUC(java.util.concurrent)包提供了比 synchronized 更灵活的通信机制:

class BoundedBuffer {
    private final Lock lock = new ReentrantLock();
    private final Condition notFull = lock.newCondition();
    private final Condition notEmpty = lock.newCondition();
    private final Object[] buffer = new Object[10];
    private int count = 0;
public void put(Object item) throws InterruptedException {
    lock.lock();
    try {
        while (count == buffer.length) {
            notFull.await(); // 满了就等待
        }
        buffer[count++] = item;
        notEmpty.signal(); // 通知消费者
    } finally {
        lock.unlock();
    }
}

public Object take() throws InterruptedException {
    lock.lock();
    try {
        while (count == 0) {
            notEmpty.await(); // 空了就等待
        }
        Object item = buffer[--count];
        notFull.signal(); // 通知生产者
        return item;
    } finally {
        lock.unlock();
    }
}

}

优势:一个锁可以创建多个 Condition,实现精确唤醒(比如生产者只唤醒消费者,不唤醒其他生产者),比 notifyAll() 效率更高。


基于 JUC 同步工具的通信

CountDownLatch(倒计时门闩)

一个线程等待多个线程完成:

CountDownLatch latch = new CountDownLatch(3);

// 工作线程完成后调用
latch.countDown();

// 主线程等待所有工作线程完成
latch.await();

CyclicBarrier(循环栅栏)

多个线程互相等待,到齐后一起执行:

CyclicBarrier barrier = new CyclicBarrier(3, () -> {
    System.out.println("所有线程到齐,开始执行");
});

barrier.await(); // 每个线程到达后等待

Semaphore(信号量)

控制同时访问资源的线程数量:

Semaphore semaphore = new Semaphore(3); // 最多3个线程同时访问
semaphore.acquire(); // 获取许可
semaphore.release(); // 释放许可

基于管道流的通信

Java 提供了线程间数据传输的管道流:

PipedOutputStream pos = new PipedOutputStream();
PipedInputStream pis = new PipedInputStream(pos);

// 线程A写入数据
pos.write("Hello from Thread A".getBytes());

// 线程B读取数据
byte[] buf = new byte[1024];
int len = pis.read(buf);


对比总结

通信方式 适用场景 特点
wait/notify 简单的生产者-消费者 必须配合 synchronized,只能随机唤醒
volatile 线程间的状态标志位 轻量,仅保证可见性,不保证原子性
Lock + Condition 需要精确唤醒的复杂场景 灵活高效,支持多条件队列
CountDownLatch 一个线程等待多个线程 一次性使用,不可重置
CyclicBarrier 多线程互等到齐 可循环使用
Semaphore 限流/资源池 控制并发数量
管道流 线程间传输字节/字符数据 面向流的通信

 总结:Java 线程通信的本质是共享内存 + 同步协调。底层依赖 JVM 的 Monitor 机制和内存模型(JMM),上层通过 JUC 提供了更丰富、更易用的同步工具。实际开发中,推荐优先使用 JUC 包中的高级工具(如 ConditionCountDownLatch 等),它们比原始的 wait/notify 更安全、更灵活。