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 包中的高级工具(如 Condition、CountDownLatch 等),它们比原始的 wait/notify 更安全、更灵活。