Java线程顺序控制完全指南:7种方法精准编排执行次序
Java线程顺序控制完全指南:7种方法精准编排执行次序
|
🌺The Begin🌺点点关注,收藏不迷路🌺
|
📌 本文导读:本文将系统讲解Java中控制多个线程执行顺序的7种方法,从传统的
join()到现代的CompletableFuture,涵盖各种场景下的最佳实践。全文包含五大核心章节、3个彩色流程图、9个代码示例。预计阅读时间28分钟。
一、🔴 线程顺序控制概述:为什么需要控制?
1.1 🟠 问题的本质
在多线程编程中,线程的执行顺序是由操作系统调度器决定的,不可预测且不确定。这就像多个运动员在跑道上各自奔跑,没有统一的发令和节奏控制。
然而,在实际业务场景中,我们常常需要精确控制线程的执行次序:
| 场景 | 需求 |
|---|---|
| 任务依赖 | 任务B依赖任务A的计算结果 |
| 数据准备 | 数据加载完成后才能进行数据处理 |
| 多阶段处理 | 流水线各阶段按顺序执行 |
| 主线程汇总 | 所有子线程完成后主线程才能继续 |
1.2 🟡 线程无序执行的演示
// 🔴 验证线程的执行顺序不可预测
public class UnorderedThreadDemo {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
final int id = i;
new Thread(() -> {
System.out.print(id + " ");
}, "Thread-" + id).start();
}
// 每次运行输出顺序可能不同:0 4 2 1 3 或 2 0 1 3 4 ...
}
}
📌 结论:线程启动的先后顺序不等于执行的先后顺序。必须使用同步机制来保证顺序。
二、🔵 方法一:join() —— 等待线程结束
原理:join()方法使当前线程阻塞等待,直到目标线程执行完毕。这种方法能够强制"后发先至"的线程等待"先发后至"的线程完成。
// 🟢 使用join保证执行顺序:T1 → T2 → T3
public class JoinOrderDemo {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
System.out.println("线程1 执行");
}, "T1");
Thread t2 = new Thread(() -> {
System.out.println("线程2 执行");
}, "T2");
Thread t3 = new Thread(() -> {
System.out.println("线程3 执行");
}, "T3");
t1.start();
t1.join(); // 主线程等待t1结束
t2.start();
t2.join(); // 主线程等待t2结束
t3.start();
t3.join(); // 主线程等待t3结束
// 输出必定是:线程1 执行 → 线程2 执行 → 线程3 执行
}
}
适用场景:线程数量较少、简单的顺序控制。
局限性:只能控制主线程与子线程之间的关系,无法实现线程间的复杂协作。
三、🟣 方法二:CountDownLatch —— 倒计时门闩
原理:初始化一个计数器,线程调用await()阻塞等待,其他线程调用countDown()使计数器减1。当计数器归零时,所有等待线程被唤醒。
// 🟠 使用CountDownLatch:等待3个初始化线程完成后再执行主逻辑
public class CountDownLatchOrderDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3);
// 启动3个初始化线程
for (int i = 0; i < 3; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " 初始化完成");
latch.countDown(); // 计数器减1
}, "Init-" + i).start();
}
// 主线程等待所有初始化完成
latch.await();
System.out.println("所有初始化完成,主线程继续执行");
}
}
适用场景:主线程等待多个子线程完成,或"发令枪"场景。
⚠️ 注意:
CountDownLatch的计数器不可重置,一次性使用。
四、🟠 方法三:CyclicBarrier —— 循环栅栏
原理:所有线程在屏障处等待,直到全部到达后才一起放行。支持重复使用和屏障动作。
// 🔵 使用CyclicBarrier控制多阶段处理
public class CyclicBarrierOrderDemo {
public static void main(String[] args) {
int numWorkers = 3;
CyclicBarrier barrier = new CyclicBarrier(numWorkers, () -> {
System.out.println("🟡 所有线程已到达屏障,进入下一阶段");
});
for (int i = 0; i < numWorkers; i++) {
new Thread(new Worker(i, barrier)).start();
}
}
}
class Worker implements Runnable {
private final int id;
private final CyclicBarrier barrier;
public Worker(int id, CyclicBarrier barrier) {
this.id = id;
this.barrier = barrier;
}
@Override
public void run() {
for (int stage = 0; stage < 3; stage++) {
System.out.println("Worker-" + id + " 执行阶段" + stage);
try {
Thread.sleep(1000);
barrier.await(); // 等待其他线程
} catch (Exception e) {
// 处理异常
}
}
}
}
适用场景:多阶段并行计算,每个阶段都需要所有线程同步。
五、🟢 方法四:Semaphore —— 信号量
原理:通过控制许可证的获取顺序,实现线程的先后执行。
// 🟣 用Semaphore实现交替执行:A→B→C
public class SemaphoreOrderDemo {
private static final Semaphore semA = new Semaphore(1); // A先持有许可
private static final Semaphore semB = new Semaphore(0);
private static final Semaphore semC = new Semaphore(0);
public static void main(String[] args) {
new Thread(() -> {
try {
semA.acquire();
System.out.print("A");
semB.release();
} catch (InterruptedException e) {}
}).start();
new Thread(() -> {
try {
semB.acquire();
System.out.print("B");
semC.release();
} catch (InterruptedException e) {}
}).start();
new Thread(() -> {
try {
semC.acquire();
System.out.print("C");
} catch (InterruptedException e) {}
}).start();
}
}
适用场景:需要精确控制多个线程按特定顺序交替执行的场景。
六、🟤 方法五:wait/notify —— 内置锁通信
原理:通过对象锁的wait()/notify()方法实现线程间的精准通信。
// 🔴 使用wait/notify实现顺序执行:线程1 → 线程2
public class WaitNotifyOrderDemo {
private static final Object lock = new Object();
private static boolean isFirstDone = false;
public static void main(String[] args) {
Thread t2 = new Thread(() -> {
synchronized (lock) {
while (!isFirstDone) {
try {
lock.wait(); // 等待t1完成信号
} catch (InterruptedException e) {}
}
System.out.println("线程2 执行");
}
});
Thread t1 = new Thread(() -> {
synchronized (lock) {
System.out.println("线程1 执行");
isFirstDone = true;
lock.notify(); // 唤醒t2
}
});
t2.start(); // t2先启动,但会等待
t1.start(); // t1后启动,先执行
}
}
适用场景:简单线程协调,但需注意必须在synchronized块中调用。
七、🟡 方法六:Condition —— 更灵活的等待/通知
原理:Condition是ReentrantLock提供的等待/通知机制,支持多路等待和精准唤醒。
// 🔵 使用Condition实现顺序控制
public class ConditionOrderDemo {
private static final ReentrantLock lock = new ReentrantLock();
private static final Condition condition = lock.newCondition();
private static boolean isReady = false;
public static void main(String[] args) {
Thread waiter = new Thread(() -> {
lock.lock();
try {
while (!isReady) {
condition.await(); // 等待条件
}
System.out.println("等待线程继续执行");
} catch (InterruptedException e) {}
finally {
lock.unlock();
}
});
Thread signaler = new Thread(() -> {
lock.lock();
try {
Thread.sleep(1000);
isReady = true;
condition.signal(); // 唤醒等待的线程
System.out.println("发送唤醒信号");
} catch (InterruptedException e) {}
finally {
lock.unlock();
}
});
waiter.start();
signaler.start();
}
}
适用场景:需要多条件精准控制的复杂场景。
八、🔵 方法七:CompletableFuture —— 异步编排(最现代)
原理:通过thenApply、thenCompose、thenCombine等链式方法,将异步任务按依赖关系串联起来。
// 🟣 使用CompletableFuture编排任务顺序
public class CompletableFutureOrderDemo {
public static void main(String[] args) {
// 任务链:step1 → step2 → step3
CompletableFuture<String> future = CompletableFuture
.supplyAsync(() -> {
System.out.println("步骤1: 读取数据");
return "原始数据";
})
.thenApplyAsync(data -> {
System.out.println("步骤2: 处理数据");
return data + " → 处理后数据";
})
.thenApplyAsync(data -> {
System.out.println("步骤3: 保存数据");
return data + " → 已保存";
});
// 并行执行两个独立任务,然后合并
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> 10);
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> 20);
future1.thenCombine(future2, (r1, r2) -> {
System.out.println("合并结果: " + r1 + " + " + r2);
return r1 + r2;
}).thenAccept(System.out::println);
// 等待完成
future.join();
}
}
适用场景:复杂的异步任务编排,是现代Java并发编程的首选方案。
九、🟠 流程图:7种方法选型决策树
十、🔴 避坑指南与最佳实践
10.1 🟠 常见陷阱
| 序号 | 陷阱 | 正确做法 |
|---|---|---|
| ① | 在循环外使用wait() |
必须用while循环包裹,防止虚假唤醒 |
| ② | 忘记在finally中释放锁 |
ReentrantLock必须try-finally释放 |
| ③ | 混用不同的同步机制 | 同一场景尽量使用同一种机制 |
| ④ | 为每个任务创建ForkJoinPool |
优先使用commonPool() |
10.2 🟢 选型建议
| 场景 | 推荐方法 |
|---|---|
| 少量线程简单顺序 | join() |
| 主线程等待多个子线程完成 | CountDownLatch |
| 多阶段同步 | CyclicBarrier |
| 精确的线程交替执行 | Semaphore |
| 复杂的异步任务编排 | CompletableFuture ⭐ |
| 需要多条件等待 | Condition |

|
🌺The End🌺点点关注,收藏不迷路🌺
|
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)