多线程乱成一锅粥?教你把线程按顺序乖乖排队!

原文来自于:zha-ge.cn/java/94

多线程乱成一锅粥?教你把线程按顺序乖乖排队!

不瞒你说,搞 Java 多线程这么多年,实际上------每次项目一但挨上"顺序执行线程"这活儿,我心里还是要打个突。明明大家都是线程,凭啥你就非要先我一步,把那个开关拧掉?

我先来点题吧:今天聊聊"多个线程如何按顺序执行"这档事,顺带聊点自己曾经踩过的那些坑,最后教你几招安安稳稳排个顺序。


线程乱炖的第一现场

事情还得从半年前一个改接口加日志的需求说起------

日志要打印A-B-C用户行为,每一步是不同的Service,老板要求:必须严格按A-B-C顺序输出!

"啊这,不就是'顺序执行'嘛!"我心里一乐,随手写了点线程池丢进去,各线程代码长这样:

java 复制代码
new Thread(() -> {
    // 处理A
    System.out.println("A");
}).start();
new Thread(() -> {
    // 处理B
    System.out.println("B");
}).start();
// C同理......

执行结果嘛,你猜怎么着:

有时候是A-B-C,有时候B-A-C,偶尔来了个C-A-B...... 日志直接变成烩面,不知道到底是哪家用户干了啥。


"只要用 synchronized,就能排队"吗?

当时一拍大腿:同步锁大法好啊!一人写个synchronized,大家顺序走。

但你细品,锁只是让两个线程不能同时进来,并没有保证哪个线程先、哪个线程后!

  • 线程A抢占锁,输出A
  • 同一时刻线程B、C虎视眈眈
  • 谁抢到锁,是个天知道!

后来搞个"同步方法队列",或者让主线程sleep等待......诡异的并发问题还是层出不穷。


踩坑瞬间

说到这,不得不表演一波"踩坑翻车现场"了------

  1. sleep控制顺序? 想当然地开 A 线程、sleep 100 毫秒、再开 B 线程------结果运行环境一变,延迟一乱,顺序直接扑街。

  2. 自定义 flag 变量? 线程疯狂 while(flag) 死等前面线程,结果无数 CPU 吃灰,服务器呜呼哀哉。

  3. 锁里套锁,信号量自杀? 几十个线程加锁、wait、notify,一不小心忘 notify,线程全卡死,运维@你"今天是不是又写挂了?"。

踩坑总结一句话:并发顺序,想糊弄,一定要翻车!


一招定江山:用信号量"排号进场"

后来我悟了------线程排队这种事,得玩点信号同步 的花样,不能靠碰运气。 最适配的还是CountDownLatchCyclicBarrierSemaphore,尤其适合"第N步必须等前面干完再上"。

比如用CountDownLatch实现ABC顺序:

java 复制代码
CountDownLatch latchAB = new CountDownLatch(1);
CountDownLatch latchBC = new CountDownLatch(1);

new Thread(() -> {
    System.out.println("A");
    latchAB.countDown(); // 放行B
}).start();

new Thread(() -> {
    latchAB.await();
    System.out.println("B");
    latchBC.countDown(); // 放行C
}).start();

new Thread(() -> {
    latchBC.await();
    System.out.println("C");
}).start();

注意:

  • A线程干完,放行B线程(latchAB.countDown())
  • B线程等A干完,才执行,再放行C(latchBC.countDown())
  • 线程顺序如铁门槛,谁也插不队!

理论上还可以用SemaphoreReentrantLock+Condition,但CountDownLatch最容易上手。


经验启示

手法对比清单(文字版)

很多人一上来就问:"那线程顺序到底该怎么排?"我自己踩过不少坑,总结下来,大致有这么几种手法:

首先是最不推荐的 sleep 或 while 循环。 这种写法看起来像是用延时或者死等来控制顺序,实则完全靠运气。CPU 一旦调度不一样、网络环境一乱,顺序直接崩盘,而且 while 死等还会疯狂消耗 CPU。可以说是"一星警告",能不用就别碰。

接着是 synchronized。 synchronized 本质上是个互斥锁,它只能保证线程不会同时执行某一段代码,但没法保证顺序。换句话说,它只能让大家排队进门,却管不住谁先谁后。这个方法勉强能忍,但不是解决顺序问题的正道。

然后是 CountDownLatch。 这是我最常用、也是最推荐的方案。它适合那种明确的前后依赖,比如 A 干完才能轮到 B,B 干完才能轮到 C。一次性的顺序执行,用 CountDownLatch 简单高效。

再高级一点的是 CyclicBarrier。 它的特点是"可重复使用",就像一群人跑步,每圈跑完都要等大家集合齐了才能开下一轮。非常适合阶段性同步任务,不过场景要合适才行。

还有 Semaphore。 这个就像发号牌,限制一次能进多少人。用在控制并发数的时候最合适,如果你设计得巧妙,也能实现"按号排队"的效果。

最后是 ReentrantLock 搭配 Condition。 这是高阶玩家的工具,灵活度极高,你可以精确控制哪类线程该被唤醒,顺序可以自己编排。缺点是写起来比前几种都复杂,适合需要强可控性的场景。

怎么排队

多线程按顺序执行,说白了就是排队。

乱来靠运气 = 必翻车。

科学用工具(CountDownLatch、Semaphore 等)= 顺序稳如老狗。

经验之谈:别怕麻烦,写对一次比调 Bug 十次省心。

相关推荐
达文汐11 小时前
【困难】力扣算法题解析LeetCode332:重新安排行程
java·数据结构·经验分享·算法·leetcode·力扣
培风图南以星河揽胜11 小时前
Java版LeetCode热题100之零钱兑换:动态规划经典问题深度解析
java·leetcode·动态规划
启山智软11 小时前
【中大企业选择源码部署商城系统】
java·spring·商城开发
我真的是大笨蛋11 小时前
深度解析InnoDB如何保障Buffer与磁盘数据一致性
java·数据库·sql·mysql·性能优化
怪兽源码12 小时前
基于SpringBoot的选课调查系统
java·spring boot·后端·选课调查系统
恒悦sunsite12 小时前
Redis之配置只读账号
java·redis·bootstrap
梦里小白龙12 小时前
java 通过Minio上传文件
java·开发语言
人道领域12 小时前
javaWeb从入门到进阶(SpringBoot事务管理及AOP)
java·数据库·mysql
sheji526112 小时前
JSP基于信息安全的读书网站79f9s--程序+源码+数据库+调试部署+开发环境
java·开发语言·数据库·算法
毕设源码-邱学长12 小时前
【开题答辩全过程】以 基于Java Web的电子商务网站的用户行为分析与个性化推荐系统为例,包含答辩的问题和答案
java·开发语言