为了账号安全,请及时绑定邮箱和手机立即绑定

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

标签:
Java JavaScript

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

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

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

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


线程乱炖的第一现场

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

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

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

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顺序:

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 十次省心。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号

举报

0/150
提交
取消