java如何实现子线程执行完毕后再执行主线程

本文探讨了Java中主线程等待子线程执行完毕的四种方法:join(), CountDownLatch, CyclicBarrier和Future模式。每种方法都有其适用场景,如join()用于简单同步,CountDownLatch适用于多个子线程的同步,CyclicBarrier支持循环同步,而Future模式则用于获取异步任务的结果。

子线程常用来执行耗时任务,有时主线程需要子线程执行完毕后的结果再执行,现在来探讨实现该场景的几种实现方式

一、join()

说明:Thread中的join()方法就是同步,它使得线程之间由并行执行变为串行执行。

测试代码如下:

object ThreadTest {

    @JvmStatic
    fun main(args: Array<String>) {
        val t = Thread(SubThread())
        t.start()

        mainWork()

        t.join()

        println("Now all thread done!")
    }


    // 子线程
    class SubThread : Runnable {
        override fun run() {
            println("Sub thread is starting!")

            try {

                Thread.sleep(5000L)

            } catch (e: InterruptedException) {

                e.printStackTrace()

            }

            println("Sub thread is stopping!")
        }

    }

    // 主线程
    private fun mainWork() {
        println("Main thread start work!")
        //sleep
        Thread.sleep(2000L)
        println("Main Thread work done!")
    }

}

二、CountDownLatch

说明:CountDownLanch 是一个倒数计数器, 给一个初始值(>=0), 然后每一次调用countDown就会减1, 这很符合等待多个子线程结束的场景: 一个线程结束的时候, countDown一次, 直到所有的线程都countDown了 , 那么所有子线程就都结束了。

      常用方法:

       await: 会阻塞等待计数器减少到0位置. 带参数的await是多了等待时间.

  countDown: 将当前的计数减1

  getCount(): 返回当前的计数

  显而易见, 我们只需要在子线程执行之前, 赋予初始化countDownLanch, 并赋予线程数量为初始值.

  每个线程执行完毕的时候, 就countDown一下.主线程只需要调用await方法, 可以等待所有子线程执行结束。

  示例:

object CountDownLatchTest {

    @JvmStatic
    fun main(args: Array<String>) {
        //定义线程数
        val subThreadNum = 5
        //取得一个倒计时器,从5开始
        val countDownLatch = CountDownLatch(subThreadNum)
        //依次创建5个线程,并启动
        for (i in 0 until subThreadNum) {
            Thread(SubThread(2000L * (i + 1), countDownLatch)).start()
        }

        mainWork()

        // 等待所有的子线程结束
        countDownLatch.await()
        println("Now all thread done!")

        /**
         * 运行结果
         * Main thread start work!
        Sub thread is starting!
        Sub thread is starting!
        Sub thread is starting!
        Sub thread is starting!
        Sub thread is starting!
        Main Thread work done!
        Now all thread done!
         */
    }


    // 子线程
    class SubThread(var workTime:Long,var countDownLatch: CountDownLatch) : Runnable {
        override fun run() {
            println("Sub thread is starting!")

            try {

                Thread.sleep(workTime)


            } catch (e: InterruptedException) {

                e.printStackTrace()

            }finally {
                countDownLatch.countDown()
            }

//            println("Sub thread is stopping!")
        }

    }



    // 主线程
    private fun mainWork() {
        println("Main thread start work!")
        //sleep
        Thread.sleep(2000L)
        println("Main Thread work done!")
    }

}

但是有一点有意思的现象,mainwork的“Main Thread work done!”总是在所有的子线程执行完毕后才执行,即使他的休眠时间比有的子线程时间少,这点令人不解。

三、CyclicBarrier

示例:

object CyclicBarrierTest {

    @JvmStatic
    fun main(args: Array<String>) {
        //定义线程数
        val subThreadNum = 5
        //取得一个倒计时器,从5开始
        val cyclicBarrier = CyclicBarrier(subThreadNum)
        //依次创建5个线程,并启动
        for (i in 0 until subThreadNum) {
            Thread(SubThread(2000L * (i + 1), cyclicBarrier)).start()
        }

        mainWork()

        // 等待所有的子线程结束
        cyclicBarrier.await()
        println("Now all thread done!")

        /**
         * 运行结果
         * Main thread start work!
        Sub thread is starting!
        Sub thread is starting!
        Sub thread is starting!
        Sub thread is starting!
        Sub thread is starting!
        Main Thread work done!
        Now all thread done!
         */
    }


    // 子线程
    class SubThread(var workTime:Long,var cyclicBarrier: CyclicBarrier) : Runnable {
        override fun run() {
            println("Sub thread is starting!")

            try {

                Thread.sleep(workTime)


            } catch (e: InterruptedException) {

                e.printStackTrace()

            }finally {
                // 到达屏障
                cyclicBarrier.await()
            }

//            println("Sub thread is stopping!")
        }

    }



    // 主线程
    private fun mainWork() {
        println("Main thread start work!")
        //sleep
        Thread.sleep(2000L)
        println("Main Thread work done!")
    }

}

说明:CountDownLatch和CyclicBarrier有什么区别呢?

他们的区别:CountDownLatch只能使用一次,而CyclicBarrier方法可以使用reset()方法重置,所以CyclicBarrier方法可以处理更为复杂的业务场景。

四、Future

说明:使用并发包下面的Future模式.

  Future是一个任务执行的结果, 他是一个将来时, 即一个任务执行, 立即异步返回一个Future对象, 等到任务结束的时候, 会把值返回给这个future对象里面. 我们可以使用ExecutorService接口来提交一个线程.(注意:Future.get()为一个阻塞方法)

  示例:

object FutureTest {

    @JvmStatic
    fun main(args: Array<String>) {
        // 创建一个为1的线程池
        val tp = Executors.newFixedThreadPool(1)
        val t = Thread(SubThread())
        val future = tp.submit(t)
        mainWork()
        // 阻塞,等待子线程结束
        future.get()
        println("Now all thread done!")
        // 关闭线程池
        tp.shutdown()
    }

    // 子线程
    class SubThread : Runnable {
        override fun run() {
            println("Sub thread is starting!")

            try {

                Thread.sleep(5000L)

            } catch (e: InterruptedException) {

                e.printStackTrace()

            }

            println("Sub thread is stopping!")
        }

    }



    // 主线程
    private fun mainWork() {
        println("Main thread start work!")
        //sleep
        Thread.sleep(2000L)
        println("Main Thread work done!")
    }

}

总结:

以上几种方式根据业务场景和需求选用合适的同步方式

参考文章:

问题:如何实现java主线程等待子线程执行完毕之后再执行?

<think>我们正在讨论Java主线程子线程执行关系。用户的问题是:主线程子线程是否可以同时执行? 根据Java线程模型,答案是肯定的:主线程子线程可以同时执行。 解释: 1. Java的多线程机制允许创建多个线程,这些线程在操作系统的调度下可以并发执行。 2. 主线程是启动其他线程线程,但主线程启动子线程后,两者是独立的执行流。 3. 除非有明确的同步机制(如join、锁等),否则主线程子线程会并行执行。 但是,我们需要注意以下几点: - 在单核CPU上,由于时间片轮转,同一时刻只有一个线程运行,但宏观上看起来是同时执行。 - 在多核CPU上,多个线程可以真正并行运行。 为了支持这个结论,我们可以参考用户提供的引用内容: 引用[1]提到主线程等待子线程执行完毕,这本身就说明主线程子线程是同时运行的,否则就不需要等待。 引用[2]中讨论了主线程结束后子线程是否继续运行,这进一步说明主线程子线程是独立的,主线程的结束并不必然导致子线程结束(除非是守护线程)。 引用[3]展示了使用join()方法让主线程等待子线程,这再次证明主线程子线程可以同时运行,否则就不需要等待。 因此,我们可以这样回答: ### Java主线程子线程执行关系 在Java中,主线程子线程**可以同时执行**,这是Java线程编程的核心特性之一。具体表现为: 1. **并发执行** - 当主线程创建并启动(`start()`)子线程后,子线程立即进入可运行状态,由操作系统调度执行。 - 此时主线程继续执行后续代码,而子线程独立执行其`run()`方法中的任务,两者**并行运行**(宏观上同时执行)。 2. **独立性** - 子线程的生命周期不受主线程结束的影响(除非子线程是**守护线程**)[^2]。 - 若子线程是**用户线程**(默认类型),即使主线程提前结束,子线程仍会继续执行直至任务完成。 - 若子线程是**守护线程**(通过`setDaemon(true)`设置),则主线程结束时,JVM会强制终止所有守护线程。 3. **同步控制** - 若需要主线程等待子线程完成(如结果汇总),需通过同步机制实现,例如: - `Thread.join()`:主线程阻塞直至目标子线程结束[^1][^3]。 - `CountDownLatch`、`CyclicBarrier`等并发工具[^5]。 - 线程池的`awaitTermination()`方法[^5]。 #### 代码示例:主线程子线程并行执行 ```java public class MainAndChildThread { public static void main(String[] args) { // 创建子线程(用户线程,默认) Thread childThread = new Thread(() -> { for (int i = 0; i < 5; i++) { System.out.println("子线程执行: " + i); try { Thread.sleep(500); // 模拟耗时操作 } catch (InterruptedException e) { e.printStackTrace(); } } }); childThread.start(); // 启动子线程 // 主线程继续执行 for (int i = 0; i < 5; i++) { System.out.println("主线程执行: " + i); try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } } // 注意:此处未使用join(),主线程不等待子线程 } } ``` **输出可能**(交替执行,证明并行): ``` 主线程执行: 0 子线程执行: 0 主线程执行: 1 子线程执行: 1 主线程执行: 2 主线程执行: 3 子线程执行: 2 主线程执行: 4 子线程执行: 3 子线程执行: 4 ``` #### 关键结论 | **场景** | **能否同时执行** | **说明** | |--------------------------|------------------|----------------------------------------| | 默认情况(无同步控制) | ✅ 能 | 主线程子线程独立运行 | | 子线程为守护线程 | ⚠️ 主线程结束即终止 | 主线程结束时,守护线程被强制终止[^2] | | 使用`join()`等待子线程 | ❌ 主线程暂停 | 主线程阻塞直至目标子线程结束[^1][^3] | > ⚠️ **注意**:实际并行程度取决于CPU核心数和操作系统调度策略。 --- ### 相关问题延伸 1. 如何让主线程等待多个子线程全部完成后再继续执行? 2. 守护线程与用户线程的应用场景有何区别? 3. 主线程如何获取子线程执行结果?[^4] 4. 使用线程池时,如何实现主线程等待所有任务完成?[^5] [^1]: 使用`Thread.join()`实现主线程等待子线程结束。 [^2]: 守护线程的生命周期依赖于创建它的线程(通常是主线程)。 [^3]: `join()`是最基础的线程同步机制。 [^4]: 可通过共享变量、`Future`或回调函数传递子线程结果。 [^5]: 线程池的`awaitTermination()`方法可阻塞主线程直至任务完成。</think>### Java主线程子线程执行关系 在 Java 中,**主线程子线程可以同时执行**,这是 Java线程编程的核心特性。具体表现为: 1. **并行执行机制** - 当主线程通过 `start()` 方法启动子线程后,子线程会立即进入**可运行状态**。 - 此时主线程**不会等待**子线程结束,而是继续执行后续代码。 - 子线程由操作系统调度器分配 CPU 时间片,与主线程**并发执行**。 ```java public class ConcurrentExample { public static void main(String[] args) { // 创建并启动子线程 Thread childThread = new Thread(() -> { for (int i = 1; i <= 3; i++) { System.out.println("子线程执行: " + i); try { Thread.sleep(500); } catch (Exception ignored) {} } }); childThread.start(); // 主线程继续执行 for (int i = 1; i <= 3; i++) { System.out.println("主线程执行: " + i); try { Thread.sleep(300); } catch (Exception ignored) {} } } } ``` **典型输出**(交替执行证明并行): ``` 主线程执行: 1 子线程执行: 1 主线程执行: 2 子线程执行: 2 主线程执行: 3 子线程执行: 3 ``` 2. **生命周期独立性** - 主线程结束**不会强制终止子线程**(除非子线程是守护线程)[^2]。 - 子线程作为**用户线程**(默认类型)时,即使主线程结束,子线程仍会继续运行直至任务完成。 - JVM 在所有用户线程结束后才会退出。 3. **同步控制场景** 当需要主线程等待子线程完成时,需通过同步机制实现: - **`Thread.join()`**:主线程阻塞直至目标子线程结束[^1][^3] - **线程池的 `awaitTermination()`**:等待线程池中所有任务完成[^5] - **并发工具类**:如 `CountDownLatch`, `CyclicBarrier` ```java // 使用 join() 实现主线程等待 public static void main(String[] args) throws InterruptedException { Thread child = new Thread(() -> System.out.println("子线程完成")); child.start(); child.join(); // 主线程在此阻塞 System.out.println("主线程继续"); } ``` 4. **执行结果传递** 主线程获取子线程执行结果的方式: - **共享变量**(需同步控制)[^4] - **`Future` 和 `Callable`**(线程池场景) - **回调函数** --- ### 关键结论 | **特性** | 说明 | |------------------------|----------------------------------------------------------------------| | **默认执行关系** | 主线程子线程并行执行 | | **主线程结束的影响** | 不影响用户线程子线程执行(守护线程会被终止)[^2] | | **强制同步方法** | 需显式调用 `join()`, `awaitTermination()` 等[^1][^3][^5] | | **结果传递机制** | 通过共享变量、Future 或回调实现[^4] | | **JVM 退出条件** | 所有用户线程结束后 JVM 才退出 | > 📌 **注意**:实际并行程度取决于 CPU 核心数和操作系统调度策略。 --- ### 相关问题延伸 1. 如何让主线程等待**多个子线程**全部完成后再继续执行? 2. 守护线程与用户线程的应用场景有何本质区别?[^2] 3. 使用线程池时,如何高效获取所有子任务的执行结果?[^4][^5] 4. 当主线程意外终止时,如何确保子线程的安全退出? [^1]: 通过 `Thread.join()` 实现主线程同步等待 [^2]: 守护线程与用户线程的生命周期差异 [^3]: `join()` 是最基础的线程同步机制 [^4]: 共享变量或 Future 实现结果传递 [^5]: 线程池的 `awaitTermination()` 方法
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值