Java中控制多线程顺序执行的六种实现方案

 更新时间:2025年09月14日 14:33:58   作者:Bug改不动了  
在多线程编程中,线程的执行顺序本质上是不确定的,由操作系统调度器决定,但在某些业务场景中,我们需要确保线程按照特定顺序执行,所以本文介绍了Java中控制多线程顺序执行的六种实现方案,需要的朋友可以参考下

一、线程顺序执行的核心挑战

在多线程编程中,线程的执行顺序本质上是不确定的,由操作系统调度器决定。但在某些业务场景中,我们需要确保线程按照特定顺序执行,例如:

  1. 分阶段任务处理(先初始化,再加载,最后执行)
  2. 事件的有序处理(保证事件处理的先后顺序)
  3. 资源的有序访问(避免竞争条件)

下面介绍的6种方法都能解决这个问题,但各有特点和适用场景。

二、6种实现方法详解

方法1:join() - 简单直接的阻塞方案

实现原理

join()方法会使当前线程等待目标线程执行完毕。通过在主线程中依次调用各个线程的join(),可以实现严格的顺序执行。

public class JoinExample {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println("第一阶段任务执行");
            // 模拟任务执行时间
            try { Thread.sleep(500); } catch (InterruptedException e) {}
        });
        
        Thread t2 = new Thread(() -> System.out.println("第二阶段任务执行"));
        Thread t3 = new Thread(() -> System.out.println("第三阶段任务执行"));
        
        try {
            System.out.println("启动第一阶段任务");
            t1.start();
            t1.join();  // 主线程在此等待t1完成
            
            System.out.println("启动第二阶段任务");
            t2.start();
            t2.join();  // 等待t2完成
            
            System.out.println("启动第三阶段任务");
            t3.start();
            t3.join();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            System.err.println("任务执行被中断");
        }
    }
}

优点

  • 实现简单直观
  • 不需要额外同步工具

缺点

  • 会阻塞主线程
  • 不够灵活,难以应对复杂场景

适用场景:简单的线性任务流,且可以接受阻塞主线程的情况。

方法2:单线程线程池 - 优雅的任务队列方案

实现原理

通过Executors.newSingleThreadExecutor()创建单线程的线程池,自然保证任务按提交顺序执行。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SingleThreadExecutorExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        
        executor.execute(() -> {
            System.out.println("准备数据");
            // 模拟耗时操作
            try { Thread.sleep(1000); } catch (InterruptedException e) {}
        });
        
        executor.execute(() -> System.out.println("处理数据"));
        executor.execute(() -> System.out.println("保存结果"));
        
        executor.shutdown();
        
        // 等待所有任务完成
        while (!executor.isTerminated()) {}
        System.out.println("所有任务已完成");
    }
}

优点

  • 自动管理线程生命周期
  • 支持任务队列
  • 避免手动创建线程

缺点

  • 无法灵活控制中间状态
  • 单线程可能成为性能瓶颈

适用场景:需要顺序执行但可能动态添加任务的场景。

方法3:CountDownLatch - 灵活的同步屏障方案

实现原理

CountDownLatch通过计数器实现线程等待,允许一个或多个线程等待其他线程完成操作。

import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {
    public static void main(String[] args) {
        // 第一个闸门,初始为1表示t1可以直接执行
        CountDownLatch latch1 = new CountDownLatch(1);
        // 第二个闸门,t2需要等待
        CountDownLatch latch2 = new CountDownLatch(1);
        
        Thread t1 = new Thread(() -> {
            System.out.println("数据库连接建立");
            latch1.countDown(); // t1完成后打开闸门1
        });
        
        Thread t2 = new Thread(() -> {
            try {
                latch1.await(); // 等待闸门1打开
                System.out.println("查询用户数据");
                latch2.countDown(); // t2完成后打开闸门2
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });
        
        Thread t3 = new Thread(() -> {
            try {
                latch2.await(); // 等待闸门2打开
                System.out.println("生成报表");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });
        
        // 故意打乱启动顺序测试
        t3.start();
        t2.start();
        t1.start();
    }
}

优点

  • 灵活控制多个线程的依赖关系
  • 支持一对多、多对多同步
  • 不阻塞主线程

缺点

  • 需要创建多个CountDownLatch对象
  • 计数器不可重置

适用场景:分阶段任务,特别是多阶段有复杂依赖关系的场景。

方法4:ReentrantLock与Condition - 精准控制的等待/通知机制

实现原理

ReentrantLock配合Condition提供了比synchronized更灵活的线程通信机制。每个Condition对象实质上是一个独立的等待队列,可以实现精确的线程唤醒控制。

import java.util.concurrent.locks.*;

public class ReentrantLockConditionExample {
    // 可重入锁
    private static Lock lock = new ReentrantLock(true); // 公平锁
    // 三个条件变量
    private static Condition condition1 = lock.newCondition();
    private static Condition condition2 = lock.newCondition();
    private static Condition condition3 = lock.newCondition();
    // 状态标志
    private static volatile int flag = 1;

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> task(1, 2, condition1, condition2));
        Thread t2 = new Thread(() -> task(2, 3, condition2, condition3));
        Thread t3 = new Thread(() -> task(3, 1, condition3, condition1));

        t1.start();
        t2.start();
        t3.start();

        // 模拟运行后停止
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.exit(0);
    }

    private static void task(int currentFlag, int nextFlag, 
                           Condition waitCondition, Condition signalCondition) {
        while (true) {
            lock.lock();
            try {
                // 检查是否轮到自己执行
                while (flag != currentFlag) {
                    waitCondition.await(); // 释放锁并等待
                }
                
                System.out.println("Thread " + currentFlag + " 正在执行");
                Thread.sleep(1000); // 模拟处理时间
                
                // 更新状态并唤醒下一个线程
                flag = nextFlag;
                signalCondition.signal();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                lock.unlock();
            }
        }
    }
}

关键点解析

  1. 公平锁:构造ReentrantLock时传入true参数创建公平锁,减少线程饥饿
  2. 条件变量:每个线程有自己专属的Condition,避免无效唤醒
  3. volatile变量:确保状态标志的可见性
  4. await/signal:精确控制线程的等待和唤醒

优点

  • 最精确的线程控制能力
  • 支持公平性配置
  • 可中断的等待机制
  • 支持多个等待条件

缺点

  • 实现复杂度高
  • 需要手动管理锁的获取和释放
  • 容易遗漏unlock导致死锁

适用场景

  • 需要精确控制线程执行顺序的复杂场景
  • 多条件等待的线程协作
  • 对公平性有要求的场景

方法5:Semaphore - 基于许可的同步控制

实现原理

Semaphore通过维护一组许可(permits)来控制线程访问。初始化时指定许可数量,线程通过acquire()获取许可,通过release()释放许可。

import java.util.concurrent.Semaphore;

public class SemaphoreExample {
    // 初始化信号量:t1可以直接运行,t2和t3需要等待
    private static Semaphore s1 = new Semaphore(1);
    private static Semaphore s2 = new Semaphore(0);
    private static Semaphore s3 = new Semaphore(0);

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            try {
                s1.acquire(); // 获取许可
                System.out.println("线程1:数据加载");
                Thread.sleep(1000);
                s2.release(); // 释放t2的许可
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        Thread t2 = new Thread(() -> {
            try {
                s2.acquire();
                System.out.println("线程2:数据处理");
                Thread.sleep(1000);
                s3.release();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        Thread t3 = new Thread(() -> {
            try {
                s3.acquire();
                System.out.println("线程3:结果保存");
                Thread.sleep(1000);
                s1.release(); // 循环执行时可重新开始
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        // 启动线程
        t1.start();
        t2.start();
        t3.start();

        // 允许程序运行一段时间后退出
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

进阶用法

  1. 公平模式new Semaphore(1, true)
  2. 批量获取acquire(int permits)
  3. 非阻塞获取tryAcquire()
  4. 超时控制tryAcquire(long timeout, TimeUnit unit)

优点

  • 灵活控制并发度
  • 支持许可的申请和释放
  • 可实现资源池等复杂模式

缺点

  • 需要仔细设计许可数量

方法6:CompletableFuture - 函数式异步编程

实现原理

CompletableFuture是Java 8引入的增强版Future,支持函数式编程风格的任务编排。通过thenRunthenApply等方法链式组合多个异步任务。

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class CompletableFutureExample {
    public static void main(String[] args) {
        // 创建异步任务链
        CompletableFuture<Void> taskChain = CompletableFuture
            .runAsync(() -> {
                System.out.println("任务1:初始化系统");
                sleep(1000);
            })
            .thenRunAsync(() -> {
                System.out.println("任务2:加载配置");
                sleep(1500);
            })
            .thenRunAsync(() -> {
                System.out.println("任务3:启动服务");
                sleep(500);
            })
            .thenRunAsync(() -> {
                System.out.println("任务4:运行监控");
            });
        
        // 等待所有任务完成
        try {
            taskChain.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
    
    private static void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

优点

  • 声明式编程风格
  • 强大的任务组合能力
  • 内置异常处理机制
  • 与现代Java特性完美集成

缺点

  • 学习曲线较陡峭
  • 调试相对困难
  • Java 8+才支持

适用场景

  • 现代Java应用开发
  • 复杂的异步任务编排
  • 需要组合多个异步结果的场景
  • 响应式编程基础

三、常见问题与解决方案

Q1:为什么await()要在while循环中调用?

A:这是为了防止"虚假唤醒"(spurious wakeup),即线程可能在没有收到通知的情况下被唤醒。while循环会重新检查条件,确保条件真正满足。

Q2:如何避免死锁?

A:遵循以下原则:

  • 按固定顺序获取多个锁
  • 设置锁超时时间
  • 避免在锁中调用外部方法
  • 使用tryLock()替代lock()

以上就是Java中控制多线程顺序执行的六种实现方案的详细内容,更多关于Java控制多线程顺序执行的资料请关注脚本之家其它相关文章!

相关文章

  • Java8之lambda表达式基本语法

    Java8之lambda表达式基本语法

    本文通过示例大家给大家介绍了java8之lambda表达式的基本语法,感兴趣的的朋友一起看看吧
    2017-08-08
  • Java 超详细讲解抽象类与接口的使用

    Java 超详细讲解抽象类与接口的使用

    对于面向对象编程来说,抽象是它的一大特征之一,在 Java 中可以通过两种形式来体现OOP的抽象:接口和抽象类,下面这篇文章主要给大家介绍了关于Java入门基础之抽象类与接口的相关资料,需要的朋友可以参考下
    2022-04-04
  • Java 实现Redis存储复杂json格式数据并返回给前端

    Java 实现Redis存储复杂json格式数据并返回给前端

    这篇文章主要介绍了Java 实现Redis存储复杂json格式数据并返回给前端操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-07-07
  • java获取web容器地址的方法

    java获取web容器地址的方法

    java获取web容器地址的方法,需要的朋友可以参考一下
    2013-04-04
  • Spark SQL 编程初级实践详解

    Spark SQL 编程初级实践详解

    这篇文章主要为大家介绍了Spark SQL 编程初级实践详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-04-04
  • SpringBoot + minio实现分片上传、秒传、续传功能

    SpringBoot + minio实现分片上传、秒传、续传功能

    MinIO是一个基于Go实现的高性能、兼容S3协议的对象存储,使用MinIO构建用于机器学习,分析和应用程序数据工作负载的高性能基础架构,这篇文章主要介绍了SpringBoot + minio实现分片上传、秒传、续传,需要的朋友可以参考下
    2023-06-06
  • 详解idea maven nexus 常见命令配置

    详解idea maven nexus 常见命令配置

    这篇文章主要介绍了idea maven nexus 常见命令配置的相关知识,通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-04-04
  • Java中EnumMap的使用解析

    Java中EnumMap的使用解析

    这篇文章主要介绍了Java中EnumMap的使用解析,EnumMap 是一种特殊的 Map,它要求自身所有的键来自某个枚举类型,EnumMap 的内部可以作为一个数组来实现,因此它们的性能非常好,你可以放心地用 EnumMap 来实现基于枚举的查询,需要的朋友可以参考下
    2023-11-11
  • SpringBoot结合Maven项目依赖版本冲突问题解决

    SpringBoot结合Maven项目依赖版本冲突问题解决

    本文主要介绍了SpringBoot结合Maven项目依赖版本冲突问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-06-06
  • 如何使用Spring+redis实现对session的分布式管理

    如何使用Spring+redis实现对session的分布式管理

    本篇文章主要介绍了如何使用Spring+redis实现对session的分布式管理,本文主要是在Spring中实现分布式session,采用redis对session进行持久化管理,感兴趣的小伙伴们可以参考一下
    2018-06-06

最新评论