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

原文来自于: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 十次省心。

相关推荐
向前跑丶加油2 小时前
IDEA lombok注解无效的问题,运行时提示java: 找不到符号或者方法
java·开发语言·intellij-idea
企鹅虎2 小时前
ElasticStack高级搜索教程【Java培训】
java
柯南二号3 小时前
【安装配置】【搭建本地Maven私服】
java·maven
小林z3 小时前
java中级教程-ELK高级搜索,深度详解ElasticStack技术栈
java
企鹅虎3 小时前
java中级教程-ELK高级搜索,深度详解ElasticStack技术栈
java
浩浩kids3 小时前
Scala • basis
java·开发语言·scala
小虎l3 小时前
java中级教程-ELK高级搜索,深度详解ElasticStack技术栈
java
小虎l3 小时前
【黑马程序员】Java进阶教程ELK高级搜索_ElasticStack技术栈 – 带源码课件
java
Sammyyyyy3 小时前
Go与C# 谁才更能节省内存?
java·golang·c#