线程间通信机制
本文总结了线程通信的五种核心方式:共享内存、等待通知、管道流、JUC工具类及Future异步结果。
线程间的通信(Thread Communication)是指在多线程环境中,线程之间如何交换信息、协调动作以及共享数据。
由于线程共享进程的内存空间(主要是堆内存),它们天然就可以通过读写共享变量来“通信”。但是,为了保证数据的安全性和逻辑的正确性(例如:A线程必须在B线程完成某事后才能继续执行),我们需要特定的机制来控制这种通信。
以下是几种主要的线程间通信方式(以 Java 语言为例,但概念通用于大多数编程语言):
1. 基于共享内存(Shared Memory)
这是最基础的通信方式,线程直接读写同一块内存区域。
volatile关键字(保证可见性):- 原理: 当一个变量被声明为
volatile,任何线程对它的修改都会立即刷新到主内存中,其他线程读取时会强制从主内存读取。 - 场景: 作为一个简单的信号标记(flag)。例如,线程 A 修改
stop = true,线程 B 检测到后停止运行。 - 局限: 只能保证可见性,不能保证原子性(不适合计数器等场景)。
- 原理: 当一个变量被声明为
共享对象/变量 + 锁(
synchronized/Lock):- 原理: 通过锁机制,确保同一时刻只有一个线程能修改共享变量。
- 场景: 多个线程同时修改银行账户余额。
2. 等待/通知机制(Wait/Notify)
这是经典的线程协作方式,用于解决“线程 A 需要等待线程 B 完成某项工作”的问题。
Object类的wait()和notify()/notifyAll():- 必须在
synchronized同步代码块中使用。 - wait(): 当前线程释放锁,进入等待状态,直到被唤醒。
- notify(): 随机唤醒一个在该对象上等待的线程。
- 示例(生产者-消费者模型): 队列满了,生产者
wait;消费者取走数据后,notify生产者。
- 必须在
Condition接口(配合ReentrantLock):- 比
synchronized更灵活。可以创建多个 Condition(例如notFull和notEmpty)。 - 使用
await()替代wait(),signal()替代notify()。 - 优势: 可以实现精准唤醒某类等待的线程。
- 比
3. 管道流(Piped Stream)
这是一种基于数据流的通信方式,模仿了操作系统中的管道概念。
- 类:
PipedInputStream/PipedOutputStream(字节流) 或PipedReader/PipedWriter(字符流)。 - 原理: 一个线程写入管道,另一个线程从管道读取。数据在内存中传输,不经过文件系统。
- 场景: 线程间传输大量数据,且不需要共享变量。
4. 并发工具类(JUC - java.util.concurrent)
Java 在 JDK 1.5 后引入了高级并发包,提供了更高级、更易用的通信方式:
BlockingQueue(阻塞队列):- 最推荐的方式。它是线程安全的队列。
- 原理: 当队列满时,放入元素会阻塞;当队列空时,取出元素会阻塞。
- 场景: 解耦生产者和消费者,无需手动写
wait/notify。
CountDownLatch(倒计时门闩):- 场景: 线程 A 等待其他 N 个线程都执行完任务后,才能继续执行。
- 用法: 主线程调用
await(),子线程执行完调用countDown()。
CyclicBarrier(循环栅栏):- 场景: 一组线程互相等待,直到所有线程都到达某个屏障点(Barrier),然后同时继续执行。
- 例子: 游戏加载,所有人进度条都跑完(到达屏障),游戏才开始。
Semaphore(信号量):- 场景: 控制同时访问特定资源的线程数量。
- 用法: 类似于拿许可证,处理完后归还。
5. Future 和 CompletableFuture
用于获取线程的执行结果。
Callable+Future:- 传统的
Runnable没有返回值。Callable可以返回结果。 - 主线程提交任务后,可以通过
future.get()阻塞等待子线程计算完成并获取结果。
- 传统的
CompletableFuture(Java 8+):- 支持异步回调。当线程 A 计算完成后,自动触发线程 B 执行后续操作,实现了流式的线程通信。
总结与对比
| 通信方式 | 核心机制 | 适用场景 | 复杂度 |
|---|---|---|---|
| volatile | 共享内存可见性 | 简单的状态标记(开关) | 低 |
| wait/notify | 锁 + 信号 | 精细控制线程暂停/恢复 | 高 (易出错) |
| BlockingQueue | 内部锁 + 队列 | 生产者-消费者模型 (最常用) | 低 (封装好) |
| CountDownLatch | 计数器 | 一等多 (主线程等子线程) | 中 |
| CyclicBarrier | 计数器 | 多等多 (所有线程同起跑线) | 中 |
| Pipe | 输入输出流 | 传输数据流 | 中 |
| Future | 返回值句柄 | 需要获取子线程执行结果 | 中 |
最佳实践建议:
在实际开发中,尽量避免使用底层的 wait/notify,因为容易造成死锁或逻辑错误。应优先使用 BlockingQueue 进行数据传递,使用 CompletableFuture 进行异步编排,或使用 CountDownLatch 进行流程控制。