让 3 个线程串行的几种方式

1、通过join()的方式

子线程调用join()的时候,主线程等待子线程执行完再执行。如果让多个线程顺序执行的话,那么需要他们按顺序调用start()。

   /**
     * - 第一个迭代(i=0):
     *     启动线程t1 -> 然后调用t1.join()。
     *     主线程(执行testMethod2的线程)会在t1.join()处阻塞,直到t1线程执行完毕。
     * - 第二个迭代(i=1):
     *     只有等到t1执行完毕,主线程才会继续执行,然后启动线程t2,并调用t2.join(),主线程等待t2执行完毕。
     * - 第三个迭代(i=2):
     *     同样,主线程等待t2执行完毕后,启动t3,然后等待t3执行完毕。
     * @throws InterruptedException
     */
    @Test
    void testMethod1() throws InterruptedException {
        List<Thread> list = new ArrayList<>();
        for (int i = 1; i <= 3; i++) {
            Thread thread = new Thread(() -> {
                log.info("{} 执行~", Thread.currentThread().getName());
            },"t" + i);
            list.add(thread);
        }
        for (int i = 0; i < list.size(); i++) {
            list.get(i).start();
            //他阻塞的是主线程,当前线程执行完后,主线程才会执行
            list.get(i).join();
        }
    }

2、通过CountDownLatch 的方式

countDown():没调用一次,计数器就会减 1。当计数器减到 0 时,调用await()的线程才会被唤醒。
await():当计数器不为 0 的时候,调用await()的线程会被阻塞。

   /**
     *  1、让第一个线程执行,完事调用countDown()再对countDownLatch记数减 1
     *  2、第二个线程调用await()去唤醒,但是需要线程一执行完,
     *     因为只有记数为 0 的话,当前线程才会被唤醒,依次类推
     */
    @Test
    void testMethod2(){
        CountDownLatch countDownLatch = new CountDownLatch(1);
        for (int i = 1; i <= 3; i++) {
            final int threadId = i;
            new Thread(() -> {
                if (threadId != 1) {
                    try {
                        //等待计数器归零,再继续往下执行,否则在此处阻塞
                        countDownLatch.await();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                log.info("{} 执行~", Thread.currentThread().getName());
                //计数器减 1
                countDownLatch.countDown();
            },"t" + i).start();
        }
    }

3、通过Semaphore的方式

通过信号量控制,每个线程在开始执行前需要获取一个许可,执行完毕后释放下一个线程需要的许可。
释放许可后会放到当前信号量中,下个线程可以获取许可

    /**
     * 只让第一个信号量有一个许可,另外两个信号量没有许可
     * 1、第一个线程获取到许可,执行打印,然后释放下一个信号量的许可
     * 2、第二个线程获取到许可,执行打印,然后释放下一个信号量的许可
     * 3、第三个线程获取到许可,执行打印
     */
    @Test
    void testMethod3(){
        int length = 3;
        List<Semaphore> semaphoreList = new ArrayList<>();
        for (int i = 1; i <= length; i++) {
            Semaphore semaphore = new Semaphore(i == 1 ? 1 : 0);
            semaphoreList.add(semaphore);
        }
        for (int j = 0; j < length; j++) {
            final int theadIndex = j;
            new Thread(() -> {
                try {
                    //获取许可
                    semaphoreList.get(theadIndex).acquire();
                    log.info("{} 执行~", Thread.currentThread().getName());

                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }finally {
                    //释放许可
                    if (theadIndex != length - 1) {
                        semaphoreList.get(theadIndex + 1).release();
                    }
                }
            },"t" + (j + 1)).start();
        }
    }

4、CompletableFuture的方式

原理:异步任务链式调用
优势:代码简洁,内置线程池管理

CompletableFuture.runAsync(() -> System.out.println("t1"))
    .thenRun(() -> System.out.println("t2"))
    .thenRun(() -> System.out.println("t3"))
    .join();  // 阻塞等待全部完成

5、利用线程池

原理:所有任务提交到单线程队列顺序执行
优势:自动管理线程生命周期
注意:实际是同一个线程执行所有任务

ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(() -> System.out.println("t1"));
executor.submit(() -> System.out.println("t2"));
executor.submit(() -> System.out.println("t3"));
executor.shutdown();
<think>首先,用户的问题是:“我想查找使用Java多个线程顺序执行的方法 请问Java实现多个线程顺序执行的方法”。这似乎是重复的,但核心是询问如何在Java中控制线程顺序执行。 从系统级指令,我需要: - 所有行内数学表达式使用$...$格式(但这个问题与数学无关,所以可能不需要)。 - 独立公式使用$$...$$格式(同样,可能不适用)。 - LaTeX语法正确(由于内容主要是文本和代码,确保代码块格式正确)。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识,例如[^1]。 用户提供了引用[1]到[5],这些是站内引用,我需要参考它们来回答问题。指令说:“上述引用仅供参考,并不是用户真正的需求,请尽可能多的参考用户上一次的问题和你的回答。” 用户的上一次问题是相同的查询,所以我没有之前的回答历史,但引用是提供的上下文。 总结引用内容: - [^1]:提到了join()方法、线程优先级、等待/通知链、单例线程池等。 - [^2]:讨论了join()、CountDownLatch、线程池。 - [^3]:强调Java提供多种机制,从join()到Phaser,建议优先高层抽象。 - [^4]:提到了单线程池ExecutorService,以及Semaphore。 - [^5]:列出了多种方式,包括wait/notify、Condition、Semaphore、LockSupport、CyclicBarrier、AtomicInteger等。 用户的需求是找到实现多个线程顺序执行的方法。我需要从引用中提取关键方法,并结构化地回答。 回答结构: - 清晰、逐步解决问题。 - 列出主要方法。 - 提供代码示例(因为引用中有代码)。 - 确保真实可靠。 主要方法从引用中提取: 1. **使用Thread.join()**:让一个线程等待另一个结束。 2. **使用CountDownLatch**:一个线程等待多个线程完成。 3. **使用线程池(如单线程池ExecutorService)**:只有一个线程执行任务,保证顺序。 4. **使用等待/通知机制(synchronized + wait/notify)**:通过Object.wait()和Object.notify()实现顺序。 5. **使用ReentrantLock和Condition**:更灵活的锁机制。 6. **使用Semaphore**:控制访问资源的线程数。 7. **使用Phaser或CyclicBarrier**:高级同步工具。 8. **使用CompletableFuture**(从[^3]提到):高层抽象。 9. **使用LockSupport**(从[^5]提到)。 我应该覆盖最常见的几种方法,并提供简单示例。 回答大纲: 1. **介绍**:简要解释为什么需要控制线程顺序(JVM线程调度不可预测)。 2. **主要方法**:列出并描述每种方法,给出代码示例。 3. **总结**:建议优先使用高层抽象。 4. **生成相关问题**:在末尾添加相关问题。 代码格式:使用代码块,如示例中所示。 语言:中文。 添加引用标识:在引用内容后添加[^1]、[^2]等。 相关问题生成: - 基于主题,生成类似示例的问题,如: 1. 如何选择最合适的线程同步机制? 2. Java线程安全的常见问题? 3. 高级并发工具如CompletableFuture的用法? 确保回答真实可靠:基于标准Java并发知识。 </think>### Java实现多个线程顺序执行的方法 在Java中,JVM的线程调度策略是基于优先级的抢占式调度,同优先级线程执行顺序不可预测[^1]。为了实现多个线程的顺序执行,开发者可以利用Java并发包(java.util.concurrent)提供的多种同步机制。以下是几种核心方法,基于引用内容整理,每种方法都附有代码示例和简要说明。优先推荐使用高层抽象(如线程池或CompletableFuture),以确保代码简洁和安全[^3]。 #### 1. **使用Thread.join()方法** 这是最简单的方法,通过让一个线程调用另一个线程的`join()`方法,等待其执行完毕后再启动后续线程。 **原理**:主线程或当前线程阻塞,直到目标线程终止[^1][^2]。 **代码示例**: ```java public class JoinExample { public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(() -> System.out.println("线程1执行")); Thread thread2 = new Thread(() -> System.out.println("线程2执行")); Thread thread3 = new Thread(() -> System.out.println("线程3执行")); thread1.start(); thread1.join(); // 等待thread1结束 thread2.start(); thread2.join(); // 等待thread2结束 thread3.start(); } } ``` **优点**:简单易用,无需额外库。 **缺点**:不适用于大量线程,代码可能冗余[^2]。 #### 2. **使用CountDownLatch** 适用于一个线程等待多个线程完成后继续执行。CountDownLatch通过计数器控制等待[^2]。 **原理**:初始化一个计数器,线程完成时调用`countDown()`,主线程通过`await()`等待计数器归零[^4]。 **代码示例**: ```java import java.util.concurrent.CountDownLatch; public class CountDownLatchExample { public static void main(String[] args) throws InterruptedException { CountDownLatch latch = new CountDownLatch(3); // 初始化计数器为3 System.out.println("开始执行"); new Thread(() -> { System.out.println("A"); latch.countDown(); }).start(); new Thread(() -> { System.out.println("B"); latch.countDown(); }).start(); new Thread(() -> { System.out.println("C"); latch.countDown(); }).start(); latch.await(); // 等待所有线程完成 System.out.println("所有线程执行完毕"); } } ``` **优点**:灵活支持多线程并发完成后顺序触发。 **缺点**:计数器需预先设置,不适用于动态线程数[^2][^5]。 #### 3. **使用单线程池(ExecutorService)** 通过`Executors.newSingleThreadExecutor()`创建只有一个工作线程线程池,任务按提交顺序执行[^4]。 **原理**:线程池内部维护一个任务队列,确保任务串行执行[^3]。 **代码示例**: ```java import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class SingleThreadPoolExample { public static void main(String[] args) { ExecutorService executor = Executors.newSingleThreadExecutor(); Runnable task1 = () -> System.out.println("线程1"); Runnable task2 = () -> System.out.println("线程2"); Runnable task3 = () -> System.out.println("线程3"); executor.submit(task1); executor.submit(task2); executor.submit(task3); executor.shutdown(); // 关闭线程池 } } ``` **优点**:高层抽象,自动处理线程生命周期,代码简洁安全。 **缺点**:线程池资源需手动管理,异常可能导致新线程创建[^4][^3]。 #### 4. **使用等待/通知机制(synchronized + wait/notify)** 通过Object的`wait()`和`notify()`方法实现线程间协调,构建顺序链[^1]。 **原理**:每个线程等待前一个线程的通知信号[^5]。 **代码示例**: ```java public class WaitNotifyExample { private static final Object lock = new Object(); private static int current = 1; public static void main(String[] args) { new Thread(() -> print("线程1", 1)).start(); new Thread(() -> print("线程2", 2)).start(); new Thread(() -> print("线程3", 3)).start(); } private static void print(String message, int order) { synchronized (lock) { while (current != order) { // 检查顺序 try { lock.wait(); // 等待通知 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } System.out.println(message); current++; lock.notifyAll(); // 唤醒所有等待线程 } } } ``` **优点**:灵活控制复杂顺序逻辑。 **缺点**:易出错(如死锁),需手动处理同步[^1][^3]。 #### 5. **使用ReentrantLock和Condition** 替代synchronized的更灵活锁机制,Condition提供精准的线程唤醒[^1]。 **原理**:每个线程绑定一个Condition,按顺序唤醒[^3]。 **代码示例**: ```java import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; public class ConditionExample { private static final ReentrantLock lock = new ReentrantLock(); private static final Condition condition = lock.newCondition(); private static int flag = 1; public static void main(String[] args) { new Thread(() -> runTask("线程1", 1)).start(); new Thread(() -> runTask("线程2", 2)).start(); new Thread(() -> runTask("线程3", 3)).start(); } private static void runTask(String msg, int target) { lock.lock(); try { while (flag != target) { condition.await(); // 等待信号 } System.out.println(msg); flag++; condition.signalAll(); // 唤醒所有 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { lock.unlock(); } } } ``` **优点**:高性能,支持公平锁。 **缺点**:代码复杂度高,需确保锁释放[^3]。 #### 6. **使用Semaphore或Phaser** - **Semaphore**:控制资源访问,通过许可数实现顺序[^4][^5]。 **示例**:初始化Semaphore(1),线程获取许可后执行。 - **Phaser**:高级屏障,适用于分阶段任务[^3]。 **优先推荐**: Semaphore更简单,Phaser适合复杂场景。 #### 总结建议 - **简易场景**:优先使用`Thread.join()`或单线程池(`Executors.newSingleThreadExecutor()`),代码简单且安全[^4]。 - **复杂协调**:选择`CountDownLatch`或`Condition`,支持精细控制[^2][^3]。 - **最佳实践**:避免底层同步(如wait/notify),选择高层工具如`CompletableFuture`(可实现链式顺序),并注意线程安全和资源泄漏问题[^3]。测试执行顺序至关重要,例如使用JUnit并发测试[^3]。 通过以上方法,开发者可以根据任务规模和复杂度选择合适的方案。如需进一步优化,参考Java并发包的官方文档。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值