🌺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 —— 更灵活的等待/通知

原理ConditionReentrantLock提供的等待/通知机制,支持多路等待精准唤醒

// 🔵 使用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 —— 异步编排(最现代)

原理:通过thenApplythenComposethenCombine等链式方法,将异步任务按依赖关系串联起来。

// 🟣 使用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种方法选型决策树

简单

1对1

1对多

中等

单次

可重用

复杂

📋 控制线程执行顺序

场景复杂度

线程数量

🟢 join方法

🟠 CountDownLatch

是否多阶段

🟣 CyclicBarrier

异步编排

🟡 CompletableFuture

🔵 Condition
或 wait/notify

✅ 简单直观
❌ 仅限父子线程

✅ 一次性等待
❌ 不可重置

✅ 可重用
❌ 固定参与者

✅ 链式编排
❌ 学习曲线陡


十、🔴 避坑指南与最佳实践

10.1 🟠 常见陷阱

序号 陷阱 正确做法
在循环外使用wait() 必须用while循环包裹,防止虚假唤醒
忘记在finally中释放锁 ReentrantLock必须try-finally释放
混用不同的同步机制 同一场景尽量使用同一种机制
为每个任务创建ForkJoinPool 优先使用commonPool()

10.2 🟢 选型建议

场景 推荐方法
少量线程简单顺序 join()
主线程等待多个子线程完成 CountDownLatch
多阶段同步 CyclicBarrier
精确的线程交替执行 Semaphore
复杂的异步任务编排 CompletableFuture
需要多条件等待 Condition

在这里插入图片描述


🌺The End🌺点点关注,收藏不迷路🌺

Logo

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

更多推荐