澳五机器人 Java 多线程开发系列之七:玩转多线程(线程的协作)
结合你之前关注的Java多线程并发编程中store/load与RMW操作、工业级高并发场景落地的相关背景,
这篇内容从最基础的原生等待通知机制讲起,逐步延伸到JUC高阶同步工具的实战用法,把线程之间从
“各自独立运行”到“精准协同配合”的完整知识体系讲透,完全避开零散API堆砌的问题,所有案例
都贴合真实生产环境的使用场景。
一、线程协作的核心本质
之前我们接触的多线程代码,大多是多个线程各自独立执行任务,互相之间没有任何交互,但真实的高
并发业务场景里,绝大多数任务都需要多个线程配合完成:比如经典的生产者-消费者场景,生产线程往
队列里放数据,消费线程从队列里取数据处理,两个线程的执行节奏必须精准协同,不能出现消费线程
空转浪费CPU,也不能出现生产线程往满队列里重复丢数据的问题,这就是线程协作要解决的核心问题。
二、最基础的原生协作:wait() / notify() 等待通知机制
这是Java语言从JDK1.0就内置的最基础线程协作能力,所有对象都天然自带这套机制,核心规则非常清晰:
必须在synchronized同步块内部调用wait()和notify()方法,否则会直接抛出IllegalMonitorStateException
异常,因为这两个方法的执行前提是线程已经拿到了对象的监视器锁。
线程调用obj.wait()之后,会立刻释放持有的对象锁,进入该对象的等待队列,直到其他线程调用
obj.notify()/notifyAll(),才会从等待队列里被唤醒,重新去竞争对象锁,拿到锁之后才能从wait()方
法处继续向下执行。
生产级代码里永远不要用if条件判断直接包裹wait(),必须用while循环做条件二次校验,避免线程被虚假唤
醒后直接绕过条件判断,引发队列溢出的生产故障。
标准生产者-消费者基础写法示例:
java
// 共享任务队列
Queue<String> taskQueue = new LinkedList<>();
int MAX_SIZE = 10;
// 生产者线程
new Thread(() -> {
synchronized (taskQueue) {
while (taskQueue.size() == MAX_SIZE) {
taskQueue.wait(); // 队列满就等待,释放锁
}
taskQueue.add(UUID.randomUUID().toString());
taskQueue.notifyAll(); // 唤醒所有等待的消费线程
}
}).start();
// 消费者线程
new Thread(() -> {
synchronized (taskQueue) {
while (taskQueue.isEmpty()) {
taskQueue.wait(); // 队列为空就等待
}
String task = taskQueue.poll();
taskQueue.notifyAll(); // 唤醒生产者线程
}
}).start();
三、JUC高阶协作工具类实战
从JDK1.5开始,java.util.concurrent包提供了一系列专为复杂线程协作设计的高阶工具,完全可以替代原生等
待通知的手写逻辑,代码更简洁、性能更稳定:
CountDownLatch 倒数计数器:适用于“一个主线程等待N个子线程全部执行完成再继续”的场景,比如并行
拉取多个远程接口的数据,所有接口返回之后再做结果聚合。主线程调用await()进入阻塞,每个子线程执行完
成后调用countDown()把计数器减1,计数器归0时主线程自动被唤醒,比手写wait/notify逻辑简洁10倍。
CyclicBarrier 循环屏障:适用于“N个线程互相等待,所有线程都到达屏障点之后,再同时继续向下执行”的
场景,比如多线程并行计算大文件分片,所有分片计算完成之后,再统一启动结果合并线程。和CountDownLatch
最大的区别是它可以循环复用,计数器归0之后自动重置,适合重复执行的批量任务场景。
Semaphore 信号量:适用于“控制同时访问共享资源的线程数量”的协作场景,比如数据库连接池最多允许10个
线程同时获取连接,超过的线程就阻塞等待,本质上就是一个带许可计数的流量控制器。
Exchanger 双线程交换器:专为两个线程的数据交换场景设计,两个线程各自执行到exchange()方法时会互相等
待,双方都到达之后就可以交换各自传入的数据,非常适合并行数据校验场景,两个线程分别从两个数据源读取同
一份数据,交换之后做一致性比对。
四、生产级避坑要点
永远不要用notify(),优先用notifyAll(),避免出现“通知丢失”的问题:如果多个线程在同一个对象上等待,
notify()只会随机唤醒一个线程,很可能唤醒的不是当前最需要被唤醒的线程,导致部分线程永远卡在等待队列里。
高并发场景下优先用JUC工具类替代手写wait/notify逻辑,JUC的协作工具底层基于AQS队列实现,比原生监
视器的唤醒效率高30%以上,不会出现大量线程唤醒后无效竞争锁的“惊群效应”。
不要在业务代码里直接调用Thread类的stop()/suspend()方法强制中断协作线程,很容易导致共享对象的锁永
远无法释放,引发整个服务的死锁故障,优先用中断标记位+响应中断的方式优雅终止协作流程。
需要我为你生成CyclicBarrier多线程并行计算大文件分片的完整可运行实战代码,覆盖分片读取、并行计算、结果
聚合全流程,直接就能用于生产场景参考吗?