java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java控制多线程顺序执行

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

作者: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();
    }
}

优点

缺点

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

方法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:精确控制线程的等待和唤醒

优点

缺点

适用场景

方法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();
        }
    }
}

优点

缺点

适用场景

三、常见问题与解决方案

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

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

Q2:如何避免死锁?

A:遵循以下原则:

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

您可能感兴趣的文章:
阅读全文