如何让A、B、C三个线程按严格顺序执行(附十一种解)?

方法一:使用 join() 方法(最直接)

thread.join() 方法的作用是让当前调用线程(通常是main线程)等待 thread 线程执行终止。我们可以在启动线程后,立即让主线程等待它完成,然后再启动下一个。

csharp 复制代码
public class ThreadOrderWithJoin {
    public static void main(String[] args) throws InterruptedException {
        
        Thread a = new Thread(() -> {
            System.out.println("A is running");
        }, "Thread-A");

        Thread b = new Thread(() -> {
            System.out.println("B is running");
        }, "Thread-B");

        Thread c = new Thread(() -> {
            System.out.println("C is running");
        }, "Thread-C");

        // 启动A,并等待A执行完
        a.start();
        a.join(); // main线程在此阻塞,直到线程A执行完毕

        // A执行完了,再启动B,并等待B执行完
        b.start();
        b.join(); // main线程在此阻塞,直到线程B执行完毕

        // B执行完了,最后启动C
        c.start();
        c.join(); // 如果需要等待C也执行完,就加上这行
    }
}

原理: join() 本质上是基于 wait/notify 机制实现的。它让主线程(调用者)在子线程的对象锁上等待,当子线程 run() 方法执行结束时,JVM会调用线程的 notifyAll() 方法来唤醒所有在该线程对象上等待的线程(也就是主线程),主线程从而得以继续执行。

方法二:使用 ExecutorService 单线程池(最高效、最推荐)

Java的 Executors.newSingleThreadExecutor() 提供了一个FIFO(先进先出)的任务队列 和一个工作线程。你提交的任务会按顺序依次执行,完美符合需求。

java 复制代码
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadOrderWithSingleThreadPool {
    public static void main(String[] args) {
        // 创建一个单线程的线程池
        ExecutorService executor = Executors.newSingleThreadExecutor();

        // 按顺序提交任务
        executor.submit(() -> {
            System.out.println("A is running");
        });

        executor.submit(() -> {
            System.out.println("B is running");
        });

        executor.submit(() -> {
            System.out.println("C is running");
        });

        // 关闭线程池
        executor.shutdown();
    }
}

原理: 单线程池内部维护一个阻塞队列(如 LinkedBlockingQueue )。当你调用 submit() 时,任务A、B、C被依次放入队列。池中唯一的工作线程会按FIFO顺序从队列中取出并执行这些任务。这是实现顺序执行的最简洁、高效的方式。

方法三:使用 wait()notify() / notifyAll()(最底层)

通过共享对象锁和线程间通信来手动控制顺序。这种方式更复杂,但有助于理解底层原理。

csharp 复制代码
public class ThreadOrderWithWaitNotify {
    // 共享锁对象
    private static final Object lock = new Object();
    // 标志位,控制该谁执行
    private static int currentFlag = 1;

    public static void main(String[] args) {
        Thread a = new Thread(() -> {
            synchronized (lock) {
                try {
                    // 如果不该我执行,我就等待
                    while (currentFlag != 1) {
                        lock.wait();
                    }
                    System.out.println("A is running");
                    // 修改标志位,指定下一个执行的线程(B)
                    currentFlag = 2;
                    // 唤醒所有在lock上等待的线程
                    lock.notifyAll();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread b = new Thread(() -> {
            synchronized (lock) {
                try {
                    while (currentFlag != 2) {
                        lock.wait();
                    }
                    System.out.println("B is running");
                    currentFlag = 3;
                    lock.notifyAll();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread c = new Thread(() -> {
            synchronized (lock) {
                try {
                    while (currentFlag != 3) {
                        lock.wait();
                    }
                    System.out.println("C is running");
                    // 执行完毕,可以重置标志位或结束
                    currentFlag = 1;
                    lock.notifyAll();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        // 同时启动三个线程,但它们会在锁上等待
        a.start();
        b.start();
        c.start();
    }
}

原理:

  1. 三个线程共享一把锁 lock 和一个状态标志 currentFlag
  2. 每个线程启动后都尝试获取锁,进入同步代码块。
  3. 线程检查 currentFlag ,如果不符合自己的执行条件,就调用 lock.wait() 释放锁并进入等待状态
  4. 当轮到某个线程时(例如A,currentFlag=1 ),它执行任务,然后更新 currentFlag 为下一个线程的标志,并调用 lock.notifyAll() 唤醒所有正在等待的线程
  5. 被唤醒的线程(B和C)会重新竞争锁,抢到锁的线程(比如B)会再次检查 currentFlag ,发现 currentFlag=2 符合自己的条件,于是开始执行。不符合条件的线程(C和可能又被调度到的A)会再次等待。
  6. 调用wait()和notify()方法时,必须在同步代码块中,即当前线程必须拥有对象的锁。
  7. wait()方法可以设置超时时间,超过时间线程会自动唤醒。

方法四:使用 CountDownLatch(倒计时锁存器)

java 复制代码
import java.util.concurrent.CountDownLatch;

public class ThreadOrderWithCountDownLatch {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latchAtoB = new CountDownLatch(1); // A -> B 的信号
        CountDownLatch latchBtoC = new CountDownLatch(1); // B -> C 的信号

        Thread a = new Thread(() -> {
            System.out.println("A is running");
            latchAtoB.countDown(); // 通知B可以开始了
        });

        Thread b = new Thread(() -> {
            try {
                latchAtoB.await(); // 等待A完成
                System.out.println("B is running");
                latchBtoC.countDown(); // 通知C可以开始了
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread c = new Thread(() -> {
            try {
                latchBtoC.await(); // 等待B完成
                System.out.println("C is running");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        // 启动顺序可以任意,执行顺序由 latch 控制
        c.start();
        b.start();
        a.start();
    }
}

**CountDownLatch** 是 Java 并发工具包 java.util.concurrent 中的一个同步辅助类,用于协调多个线程之间的操作。它的主要作用是让一个或多个线程等待其他线程完成任务后再继续执行。其工作机制类似于"倒计时闩锁",通过一个计数器来控制线程的同步。

核心概念与工作原理

CountDownLatch 的核心是一个计数器,初始值由构造函数指定。每当一个线程完成任务后,调用 countDown() 方法使计数器减 1。当计数器减到 0 时,所有调用 await() 方法等待的线程会被唤醒并继续执行。

  • countDown() :计数器减 1,表示一个线程完成任务。
  • await() :阻塞当前线程,直到计数器为 0。
  • 线程安全:CountDownLatch 使用内部的 AQS(AbstractQueuedSynchronizer)来保证线程安全。

方法五:使用 CompletableFuture(函数式编程风格)

Java 8+ 的 CompletableFuture 提供了链式调用的方式。

arduino 复制代码
import java.util.concurrent.CompletableFuture;

public class ThreadOrderWithCompletableFuture {
    public static void main(String[] args) {
        CompletableFuture<Void> future = CompletableFuture
            .runAsync(() -> System.out.println("A is running"))
            .thenRun(() -> System.out.println("B is running"))
            .thenRun(() -> System.out.println("C is running"));

        future.join(); // 等待所有任务完成
    }
}

**CompletableFuture** 是Java 8中引入的一个非常强大的异步编程工具,它实现了 **Future**和 **CompletionStage**接口。这个类提供了非常丰富的方法来处理异步代码执行,包括异常处理、结果合并、事件完成后的处理等。

方法六:使用 AtomicInteger + 自旋锁

通过原子变量和忙等待(busy-wait)来实现顺序控制。

java 复制代码
import java.util.concurrent.atomic.AtomicInteger;

public class ThreadOrderWithAtomicInteger {
    private static AtomicInteger counter = new AtomicInteger(1);

    public static void main(String[] args) {
        Thread a = new Thread(() -> {
            while (counter.get() != 1) {
                // 忙等待,直到轮到自己
                Thread.yield();
            }
            System.out.println("A is running");
            counter.incrementAndGet(); // counter = 2
        });

        Thread b = new Thread(() -> {
            while (counter.get() != 2) {
                Thread.yield();
            }
            System.out.println("B is running");
            counter.incrementAndGet(); // counter = 3
        });

        Thread c = new Thread(() -> {
            while (counter.get() != 3) {
                Thread.yield();
            }
            System.out.println("C is running");
            counter.incrementAndGet(); // counter = 4
        });

        a.start();
        b.start();
        c.start();
    }
}

AtomicInteger类提供了线程安全的整数操作,它通过利用底层硬件的原子性指令,能够在多线程环境中高效地实现整数的无锁更新,避免了传统同步机制带来的性能开销,在高并发场景下成为计数器可大幅提高程序的执行效率,同时又保证了数据一致性。

方法七:使用 Semaphore(信号量)

使用信号量来控制资源的访问顺序。

java 复制代码
import java.util.concurrent.Semaphore;

public class ThreadOrderWithSemaphore {
    public static void main(String[] args) throws InterruptedException {
        // 初始:A有1个许可,B和C有0个许可
        Semaphore semaphoreA = new Semaphore(1);
        Semaphore semaphoreB = new Semaphore(0);
        Semaphore semaphoreC = new Semaphore(0);

        Thread a = new Thread(() -> {
            try {
                semaphoreA.acquire(); // A获取许可
                System.out.println("A is running");
                semaphoreB.release(); // 释放B的许可
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread b = new Thread(() -> {
            try {
                semaphoreB.acquire(); // B等待许可
                System.out.println("B is running");
                semaphoreC.release(); // 释放C的许可
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread c = new Thread(() -> {
            try {
                semaphoreC.acquire(); // C等待许可
                System.out.println("C is running");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        a.start();
        b.start();
        c.start();
    }
}

Semaphore也叫信号量,在JDK1.5被引入,是用来保护一个或者多个共享资源的访问。

工作原理

信号量的核心是一个计数器:

  • 当线程调用acquire()时,计数器减 1。如果计数器小于 0,线程进入阻塞状态。
  • 当线程调用release()时,计数器加 1。如果有线程因计数器为 0 而阻塞,则会被唤醒。

Semaphore(0) 的特点是初始计数为 0,因此所有调用acquire()的线程都会立即阻塞,直到有线程调用release()

方法八:使用 CyclicBarrier(循环屏障)

虽然 CyclicBarrier 通常用于让一组线程互相等待,但可以巧妙运用来实现顺序执行。

java 复制代码
import java.util.concurrent.CyclicBarrier;

public class ThreadOrderWithCyclicBarrier {
    public static void main(String[] args) {
        CyclicBarrier barrier1 = new CyclicBarrier(2); // A和main线程
        CyclicBarrier barrier2 = new CyclicBarrier(2); // B和main线程

        Thread a = new Thread(() -> {
            try {
                System.out.println("A is running");
                barrier1.await(); // 等待main线程
            } catch (Exception e) {
                e.printStackTrace();
            }
        });

        Thread b = new Thread(() -> {
            try {
                barrier1.await(); // 等待A完成
                System.out.println("B is running");
                barrier2.await(); // 等待main线程
            } catch (Exception e) {
                e.printStackTrace();
            }
        });

        Thread c = new Thread(() -> {
            try {
                barrier2.await(); // 等待B完成
                System.out.println("C is running");
            } catch (Exception e) {
                e.printStackTrace();
            }
        });

        a.start();
        b.start();
        c.start();
    }
}

**CyclicBarrier** 是 Java 并发编程中一个重要的同步工具类,在JDK1.5被引入,允许一组线程相互等待,直到所有线程都到达某个屏障点后再继续执行。它适用于需要线程同步的场景,例如多线程任务的协调或并发模拟。 常用方法

构造方法: CyclicBarrier(int parties):指定需要等待的线程数量。 CyclicBarrier(int parties, Runnable barrierAction):额外指定屏障动作。

await() : 阻塞线程,直到所有线程到达屏障点。 支持超时版本 await(long timeout, TimeUnit unit),超时后抛出异常。

reset() :重置屏障状态,允许重新使用。

工作原理

每当线程执行await,内部变量count减1,如果count!= 0,说明有线程还未到屏障处,则在锁条件变量trip上等待。当count == 0时,说明所有线程都已经到屏障处,执行条件变量的signalAll方法唤醒等待的线程。 其中 nextGeneration方法可以实现屏障的循环使用。

和CountDownLatch的区别

  1. CountDownLatch 允许一个或多个线程等待一些特定的操作完成,而这些操作是在其它的线程中进行的,也就是说会出现 等待的线程被等的线程 这样分明的角色;
  2. CountDownLatch 构造函数中有一个 count 参数,表示有多少个线程需要被等待,对这个变量的修改是在其它线程中调用 countDown 方法,每一个不同的线程调用一次 countDown 方法就表示有一个被等待的线程到达,count 变为 0 时,latch(门闩)就会被打开,处于等待状态的那些线程接着可以执行;
  3. CountDownLatch 是一次性使用的,也就是说latch门闩只能只用一次,一旦latch门闩被打开就不能再次关闭,将会一直保持打开状态,因此 CountDownLatch 类也没有为 count 变量提供 set 的方法;

方法九:使用 BlockingQueue(阻塞队列)

利用阻塞队列的特性来控制执行顺序。

java 复制代码
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class ThreadOrderWithBlockingQueue {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> queue = new LinkedBlockingQueue<>();

        Thread a = new Thread(() -> {
            System.out.println("A is running");
            queue.offer("A_DONE");
        });

        Thread b = new Thread(() -> {
            try {
                queue.take(); // 等待A的信号
                System.out.println("B is running");
                queue.offer("B_DONE");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread c = new Thread(() -> {
            try {
                queue.take(); // 等待B的信号
                System.out.println("C is running");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        // 先启动B和C,它们会等待队列中的信号
        b.start();
        c.start();
        Thread.sleep(100); // 确保B和C先开始等待
        a.start(); // 最后启动A
    }
}

方法十:使用 Phaser(更灵活的同步器)

Java 7+ 提供的更灵活的同步器。

scss 复制代码
import java.util.concurrent.Phaser;

public class ThreadOrderWithPhaser {
     public static void main(String[] args) {
        Phaser phaser = new Phaser(1); // 注册主线程

        // 线程A
        Thread threadA = new Thread(() -> {
            System.out.println("A is running");
            phaser.arriveAndDeregister(); // A完成,注销
        });

        // 线程B
        Thread threadB = new Thread(() -> {
            phaser.register(); // 注册到Phaser
            phaser.arriveAndAwaitAdvance(); // 等待前一阶段完成
            System.out.println("B is running");
            phaser.arriveAndDeregister(); // B完成,注销
        });

        // 线程C
        Thread threadC = new Thread(() -> {
            phaser.register(); // 注册到Phaser
            phaser.arriveAndAwaitAdvance(); // 等待前一阶段完成
            phaser.arriveAndAwaitAdvance(); // 等待前一阶段完成

            System.out.println("C is running");
            phaser.arriveAndDeregister(); // C完成,注销
        });

        // 启动线程(启动顺序无关)
        threadC.start();
        threadB.start();
        threadA.start();
    }
}

Phaser,顾名思义,是一个用于阶段同步的工具。与CountDownLatch和CyclicBarrier等同步工具相比,Phaser提供了更为灵活的同步机制。它允许一组线程在多个阶段上进行同步,而不是仅仅在一个点上。这使得Phaser在处理复杂的多阶段并发任务时非常有用。

Phaser的内部工作机制

  1. 状态维护Phaser内部维护了一个复杂的状态机,包括当前阶段数、已注册的参与者数量、已到达的参与者数量等。这些状态信息用于决定何时可以进入下一个阶段。
  2. 注册与到达 :线程通过调用register()方法注册到Phaser中,并通过arrive()方法来表示它已经完成了当前阶段的工作。当所有注册的线程都调用了arrive()方法后,Phaser会推进到下一个阶段。
  3. 等待与推进 :线程可以调用awaitAdvance()方法来等待其他线程到达当前阶段,并一起进入下一个阶段。这个方法会阻塞调用线程,直到满足进入下一个阶段的条件。
  4. 中断与超时 :与其他同步工具一样,Phaser也支持响应中断和超时。这意味着如果线程在等待过程中被中断或超过指定的等待时间,它可以从等待状态中退出。

方法十一:使用 Condition 条件变量

Lock 框架下的条件变量,比 wait/notify 更灵活。

ini 复制代码
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ThreadOrderWithCondition {
    private static Lock lock = new ReentrantLock();
    private static Condition conditionA = lock.newCondition();
    private static Condition conditionB = lock.newCondition();
    private static Condition conditionC = lock.newCondition();
    private static int state = 1;

    public static void main(String[] args) {
        Thread a = new Thread(() -> {
            lock.lock();
            try {
                while (state != 1) {
                    conditionA.await();
                }
                System.out.println("A is running");
                state = 2;
                conditionB.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        });

        Thread b = new Thread(() -> {
            lock.lock();
            try {
                while (state != 2) {
                    conditionB.await();
                }
                System.out.println("B is running");
                state = 3;
                conditionC.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        });

        Thread c = new Thread(() -> {
            lock.lock();
            try {
                while (state != 3) {
                    conditionC.await();
                }
                System.out.println("C is running");
                state = 1;
                conditionA.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        });

        a.start();
        b.start();
        c.start();
    }
}

总结与对比

方法 核心机制 优点 缺点
单线程池 线程池 代码简洁、高效、优雅,利用现有框架 需要创建和管理线程池
wait/notify 底层原理 深入理解线程同步机制,灵活控制复杂流程 代码复杂,容易出错(如死锁)
join() wait/notify 机制 简单直观,易于理解 主线程会被阻塞,不能同时做其他事
CountDownLatch 倒计时等待 清晰直观,可控制多个线程 需要创建多个latch对象
CompletableFuture 函数式链式调用 代码简洁优雅,Java8+ 需要理解函数式编程
AtomicInteger 原子变量+忙等待 无锁操作,性能较好 CPU资源消耗较大(忙等待)
Semaphore 信号量许可控制 灵活,可控制并发数 需要管理多个信号量
CyclicBarrier 屏障等待 可重复使用 在此场景下稍显复杂
BlockingQueue 队列消息传递 解耦性好,扩展性强 需要处理队列异常
Phaser 阶段同步器 非常灵活,功能强大 API相对复杂
Condition 条件变量等待 比wait/notify更灵活 需要手动管理锁

推荐选择:

  • 简单场景join()CompletableFuture
  • 生产环境 :单线程池 (Executors.newSingleThreadExecutor() )
  • 复杂同步CountDownLatchPhaser
  • 学习理解wait/notifyAtomicIntegerCondition
相关推荐
步行cgn3 小时前
HttpSessionBindingListener
java·开发语言·数据仓库·servlet
浮游本尊3 小时前
Java学习第24天 - Spring Cloud Gateway与容器化部署
java
天天摸鱼的java工程师3 小时前
SpringBoot + RabbitMQ + Redis + MySQL:社交平台私信发送、已读状态同步与历史消息缓存
java·后端
JC033 小时前
JAVA解题——求阶乘和(附源代码)
java·开发语言·算法
psgogogo20253 小时前
Apache POI:Java操作Office文档的利器
java·开发语言·其他·apache
麦兜*3 小时前
Redis数据迁移实战:从自建到云托管(阿里云/腾讯云)的平滑过渡
java·spring boot·redis·spring·spring cloud·阿里云·腾讯云
间彧3 小时前
ThreadPoolTaskExecutor和ThreadPoolExecutor有何区别
java
渣哥3 小时前
多线程乱成一锅粥?教你把线程按顺序乖乖排队!
java
向前跑丶加油3 小时前
IDEA lombok注解无效的问题,运行时提示java: 找不到符号或者方法
java·开发语言·intellij-idea