让多个线程按照指定的顺序串行执行

本文详细解析了Java中Thread类的join方法如何实现线程间的串行执行,包括四个代码示例,展示了join方法的基本使用,以及如何通过主线程和子线程的交互来控制线程的执行顺序。

比如:ThreadA输出0,ThreadB输出1,ThreadC输出2……同时启动线程A、B、C……最后按启动的先后顺序输出0123456……
或者
循环new Thread(),让多个线程按new的先后顺序串行执行。


参考答案:通过Thread的join()方法实现。

代码示例一:

通过Thread的join()方法实现多个线程的串行执行。每个线程会告诉下一个线程,等我先执行。

/**
 * 通过Thread的join()方法实现多个线程的串行执行
 *
 * @author wanglingqiang
 * @date 2020/7/18 下午6:14
 **/
public class Join_Test1 {

    public static void main(String[] args) {
        System.out.println("main()开始");
        Thread previousThread = Thread.currentThread();
        for (int i = 0; i < 10; i++) {
            JoinThread joinTest = new JoinThread(previousThread, i);
            joinTest.start();
            previousThread = joinTest;
        }
        System.out.println("main()结束");
    }

    static class JoinThread extends Thread {
        int i;
        Thread previousThread; //上一个线程

        public JoinThread(Thread previousThread, int i) {
            this.previousThread = previousThread;
            this.i = i;
        }

        @Override
        public void run() {
            try {
                previousThread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("----previousThread=" + previousThread.getName() + ", currentThread=" + Thread.currentThread().getName() + ", i=" + i + "----");
        }
    }
    
}

运行结果:

/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/bin/java
main()开始
main()结束
----previousThread=main, currentThread=Thread-0, i=0----
----previousThread=Thread-0, currentThread=Thread-1, i=1----
----previousThread=Thread-1, currentThread=Thread-2, i=2----
----previousThread=Thread-2, currentThread=Thread-3, i=3----
----previousThread=Thread-3, currentThread=Thread-4, i=4----
----previousThread=Thread-4, currentThread=Thread-5, i=5----
----previousThread=Thread-5, currentThread=Thread-6, i=6----
----previousThread=Thread-6, currentThread=Thread-7, i=7----
----previousThread=Thread-7, currentThread=Thread-8, i=8----
----previousThread=Thread-8, currentThread=Thread-9, i=9----

Process finished with exit code 0

变量i是for循环new Thead()时传给线程的一个参数,打印结果是01234……说明join()保证了多个线程执行时有序串行。

代码示例二:

代码示例一的基础上,加了一点注释,帮助更进一步理解join()的用法。

/**
 * 通过Thread的join()方法实现多个线程的串行执行
 *
 * @author wanglingqiang
 * @date 2020/7/18 下午6:26
 **/
public class Join_Test2 {

    public static void main(String[] args) {
        System.out.println("main()开始");
        Thread previousThread = Thread.currentThread();
        for (int i = 0; i < 10; i++) {
            JoinThread joinTest = new JoinThread(previousThread, i);
            joinTest.start();
            previousThread = joinTest;
        }
        System.out.println("main()结束");
    }

    static class JoinThread extends Thread {
        int i;
        Thread previousThread; //上一个线程

        public JoinThread(Thread previousThread, int i) {
            this.previousThread = previousThread;
            this.i = i;
        }

        @Override
        public void run() {
            try {
                System.out.println(previousThread.getName() + ".join()开始,告诉" + Thread.currentThread().getName() + "让我先");
                previousThread.join();
                System.out.println(previousThread.getName() + ".join()结束,告诉" + Thread.currentThread().getName() + "该你了");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("----previousThread=" + previousThread.getName() + ", currentThread=" + Thread.currentThread().getName() + ", i=" + i + "----");
        }
    }
}

运行结果:

/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/bin/java
main()开始
main.join()开始,告诉Thread-0让我先
Thread-0.join()开始,告诉Thread-1让我先
Thread-1.join()开始,告诉Thread-2让我先
Thread-2.join()开始,告诉Thread-3让我先
Thread-3.join()开始,告诉Thread-4让我先
Thread-4.join()开始,告诉Thread-5让我先
Thread-5.join()开始,告诉Thread-6让我先
Thread-6.join()开始,告诉Thread-7让我先
Thread-7.join()开始,告诉Thread-8让我先
main()结束
Thread-8.join()开始,告诉Thread-9让我先
main.join()结束,告诉Thread-0该你了
----previousThread=main, currentThread=Thread-0, i=0----
Thread-0.join()结束,告诉Thread-1该你了
----previousThread=Thread-0, currentThread=Thread-1, i=1----
Thread-1.join()结束,告诉Thread-2该你了
----previousThread=Thread-1, currentThread=Thread-2, i=2----
Thread-2.join()结束,告诉Thread-3该你了
----previousThread=Thread-2, currentThread=Thread-3, i=3----
Thread-3.join()结束,告诉Thread-4该你了
----previousThread=Thread-3, currentThread=Thread-4, i=4----
Thread-4.join()结束,告诉Thread-5该你了
----previousThread=Thread-4, currentThread=Thread-5, i=5----
Thread-5.join()结束,告诉Thread-6该你了
----previousThread=Thread-5, currentThread=Thread-6, i=6----
Thread-6.join()结束,告诉Thread-7该你了
----previousThread=Thread-6, currentThread=Thread-7, i=7----
Thread-7.join()结束,告诉Thread-8该你了
----previousThread=Thread-7, currentThread=Thread-8, i=8----
Thread-8.join()结束,告诉Thread-9该你了
----previousThread=Thread-8, currentThread=Thread-9, i=9----

Process finished with exit code 0

代码示例三:

在mian()主线程里join()。每个线程都会告诉主线程,等我先执行。也能达到同样的效果。

/**
 * 在mian()主线程里join()
 *
 * @author wanglingqiang
 * @date 2020/7/18 下午6:50
 **/
public class Join_Test3 {

    public static void main(String[] args) throws InterruptedException {
        System.out.println("main()开始");
        JoinThread t1 = new JoinThread();
        t1.start();
        t1.join();
        JoinThread t2 = new JoinThread();
        t2.start();
        t2.join();
        JoinThread t3 = new JoinThread();
        t3.start();
        t3.join();
        JoinThread t4 = new JoinThread();
        t4.start();
        t4.join();
        System.out.println("main()结束");
    }

    static class JoinThread extends Thread {
        @Override
        public void run() {
            try {
                Thread.sleep(500L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName());
        }
    }

}

运行结果:

/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/bin/java
main()开始
Thread-0
Thread-1
Thread-2
Thread-3
main()结束

Process finished with exit code 0

代码示例四:

主线程和子线程的示例,累加数字。

/**
 * 累加数字
 *
 * @author wanglingqiang
 * @date 2020/7/18 下午7:00
 **/
public class Join_Test4 {

    public static volatile int num = 0;

    public static void main(String[] args) throws InterruptedException {
        System.out.println("main()开始");
        JoinThread t = new JoinThread();
        t.start();
        //t.join();
        System.out.println("num = " + num);
        System.out.println("main()结束");
    }

    static class JoinThread extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                Join_Test4.num = Join_Test4.num + 1;
            }
        }
    }

}

运行结果:

/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/bin/java
main()开始
num = 0
main()结束

Process finished with exit code 0

如果放开t.join()这一行的的注释,运行结果num = 5。


总结:

Thread类中的join()是使运行环境的线程wait()释放cpu的控制权让调用join()的线程执行直到完毕。比如在线程A中线程B调用了join(),表示只有当线程B执行完毕时,A线程才能继续执行。join(参数)就表示线程A等待超过参数时间就不在等待继续执行了,线程B还没执行完的话它俩就并行执行。注意join(0)不是等待0秒,而是线程A无限等待,直到线程B执行完成,即join(0)等价于join()。
它的主要作用就是同步,它可以使得线程之间并行执行变为串行执行。



<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、付费专栏及课程。

余额充值