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控制多线程顺序执行的资料请关注脚本之家其它相关文章!

相关文章

  • IDEA导入项目报错java程序包不存在问题及解决

    IDEA导入项目报错java程序包不存在问题及解决

    在IDEA导入项目时,若出现javafx包不存在错误,需检查并更改为JDK1.8版本(原默认为JDK14),同时确保模块目录正确、Tomcat配置无误,并补全JavaFX依赖,即可解决运行问题
    2025-09-09
  • 浅谈Java字符串比较的三种方法

    浅谈Java字符串比较的三种方法

    这篇文章主要介绍了浅谈Java字符串比较的三种方法,字符串比较是常见的操作,包括比较相等、比较大小、比较前缀和后缀串等,需要的朋友可以参考下
    2023-04-04
  • SpringBoot后端实现小程序微信登录功能实现

    SpringBoot后端实现小程序微信登录功能实现

    微信小程序登录是开发者通过微信提供的身份验证机制,获取用户唯一标识(openid)和会话密钥(session_key)的过程,这篇文章给大家介绍SpringBoot后端实现小程序微信登录功能实现,感兴趣的朋友跟随小编一起看看吧
    2025-05-05
  • SpringBoot WebSocket实时监控异常的详细流程

    SpringBoot WebSocket实时监控异常的详细流程

    最近做了一个需求,消防的设备巡检,如果巡检发现异常,通过手机端提交,后台的实时监控页面实时获取到该设备的信息及位置,然后安排员工去处理。这篇文章主要介绍了SpringBoot WebSocket实时监控异常的全过程,感兴趣的朋友一起看看吧
    2021-10-10
  • 使用Java 压缩文件打包tar.gz 包的详细教程

    使用Java 压缩文件打包tar.gz 包的详细教程

    本文带领大家学习如何使用Java 压缩文件打包tar.gz 包,主要通过 Apache compress 工具打包,通过示例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧
    2021-05-05
  • 从零搭建SpringBoot+MyBatisPlus快速开发脚手架

    从零搭建SpringBoot+MyBatisPlus快速开发脚手架

    这篇文章主要为大家介绍了从零搭建SpringBoot+MyBatisPlus快速开发脚手架示例教程,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-06-06
  • 使用Spring的FactoryBean创建和获取Bean对象方式

    使用Spring的FactoryBean创建和获取Bean对象方式

    这篇文章主要介绍了使用Spring的FactoryBean创建和获取Bean对象方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-03-03
  • Nacos框架服务注册实现流程

    Nacos框架服务注册实现流程

    这篇文章主要介绍了SpringCloud服务注册之nacos实现过程,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-08-08
  • Activiti7与Spring以及Spring Boot整合开发

    Activiti7与Spring以及Spring Boot整合开发

    这篇文章主要介绍了Activiti7与Spring以及Spring Boot整合开发,在Activiti中核心类的是ProcessEngine流程引擎,与Spring整合就是让Spring来管理ProcessEngine,有感兴趣的同学可以参考阅读
    2023-03-03
  • servlet基础知识_动力节点Java学院整理

    servlet基础知识_动力节点Java学院整理

    这篇文章主要为大家详细介绍了servlet基础的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-07-07

最新评论