JAVA8中并发类CompletableFuture使用遇到的坑-守护线程

本文探讨了Java8中CompletableFuture在并发处理时的一个问题,即默认使用ForkJoinPool线程池创建的线程是守护线程,可能导致任务在主线程结束后被提前终止。作者通过案例展示了当CompletableFuture用于长时间运行任务时,由于守护线程特性,任务会在主线程退出时被强制停止。为解决此问题,提出了自定义线程池的方式,确保任务能够持续运行。同时,文章分析了源码并提供了详细的解决方案,强调了线程池需手动关闭的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 前言

最近在看Java8中对并发的支持CompletableFuture类,觉得挺好的,在单一线程执行的时候可以省略很多代码,手动验证后,发现其中还是有一部分坑的,此处记录一下。此处主要描述其创建线程为守护线程的问题,会随着主线程消亡直接消亡,导致任务失败。

2. CompletableFuture优点

简洁,后面以代码为例

3. 案例简述

3.1 CompletableFuture创建一个线程,内部进行轮询读取任务(如:模拟kafka之类的),会发现,执行结束后,整个线程一起关闭了,无法打到一直轮询的目的。传统直接创建的线程(默认为非守护线程)会由于子线程未结束,阻止主线程关闭。

4. 代码

package com.hz.threadpool.thread;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

/**
 * @description:
 * @author: pp_lan
 * @date: 2022/3/24
 */
public class CompletableFutureTest {

    /**
     * java8并发接口
     */
    public static void main(String[] args) throws InterruptedException {
        System.out.println("============== testAsyncRunnable ===============");

        CompletableFuture.runAsync((() -> {
            while (true) {
                try {
                    TimeUnit.MILLISECONDS.sleep(1000);
                    System.out.println("**************kafka读取消息结束**************");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }));

        TimeUnit.SECONDS.sleep(3);
    }
}

5. 执行结果验证

主线程等待了3秒,子线程内部第3秒还未执行完成,随着主线程一起关闭了

Connected to the target VM, address: '127.0.0.1:64615', transport: 'socket'
============== testAsyncRunnable ===============
**************kafka读取消息结束**************
**************kafka读取消息结束**************
Disconnected from the target VM, address: '127.0.0.1:64615', transport: 'socket'

6. 源码分析

 默认的线程池为java.util.concurrent.ForkJoinPool

线程池内部创建线程的时候将线程设置为了守护线程 

7. 解决方案

7.1 方案说明

既然问题出在了自定义的线程池创建Thread的时候,那么我们可以手动创建线程池(此处也可以不可以覆写ThreadFactory),规避该问题 ,代码如下:

import java.util.concurrent.*;

/**
 * @description:
 * @author: pp_lan
 * @date: 2022/3/24
 */
public class CompletableFutureTest {

    /**
     * java8并发接口
     */
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        System.out.println("============== testAsyncRunnable ===============");

        // 使用默认线程池
//        CompletableFuture<Void> future = CompletableFuture.runAsync((() -> {
//            while (true) {
//                try {
//                    TimeUnit.MILLISECONDS.sleep(1000);
//                    System.out.println("**************kafka读取消息结束**************");
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//
//            }
//        }));



        // 使用自定义线程池
        ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 2, 3, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5),
                r -> new Thread(r));
        try {
            CompletableFuture.runAsync((() -> {
                int i = 0;
                while (i++ < 2) {
                    try {
                        TimeUnit.MILLISECONDS.sleep(1000);
                        System.out.println("**************kafka读取消息结束**************");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }
            }), pool).whenComplete((r, e) -> {
                System.out.println("执行完毕");
            });
        } finally {
            pool.shutdown();
            if (!pool.awaitTermination(10, TimeUnit.SECONDS)) {
                pool.shutdownNow();
            }
        }

    }
}

        CompletableFuture.runAsync和手动创建的线程池组合,虽然可以解决创建守护线程的问题,但是线程池还是需要手动去关闭。

7.2 源码分析

        从源码上看该方法只是将传入的线程池进行一个简单调用。从本文的案例来说,可能直接使用线程池更为直观可靠。

相关文档:守护线程(Daemon)、钩子线程(Hook)简述

### 使用 `CompletableFuture.runAsync` 并等待其完成 当使用 `CompletableFuture.runAsync` 提交任务时,可以通过调用 `.join()` 或者 `.get()` 来等待任务的完成并处理可能的结果或异常。 #### 方法一:使用 `.join()` `.join()` 方法会阻塞当前线程直到对应的异步计算完成,并返回结果。对于 `runAsync` 而言,由于其返回的是 `CompletableFuture<Void>`,因此 `.join()` 将不会有任何实际值返回,而是仅用于确认任务已完成[^2]。 ```java public class RunAsyncExample { public static void main(String[] args) throws InterruptedException { CompletableFuture<Void> future = CompletableFuture.runAsync(() -> { System.out.println("Running async task..."); try { Thread.sleep(2000); // Simulate a long-running process. } catch (InterruptedException e) { Thread.currentThread().interrupt(); } System.out.println("Task completed."); }); System.out.println("Waiting for the task to complete..."); // Wait until completion using join() future.join(); System.out.println("The asynchronous operation has finished."); } } ``` #### 方法二:使用 `.get()` 与 `.join()` 似,`.get()` 同样会使主线程暂停直至异步任务结束。不同之处在于 `.get()` 可能抛出受检异常(Checked Exception),即如果异步过程中发生了未捕获的异常,则会在调用方处被重新抛出作为 `ExecutionException` 的原因[^4]。 ```java import java.util.concurrent.ExecutionException; public class RunAsyncGetExample { public static void main(String[] args) throws InterruptedException, ExecutionException { CompletableFuture<Void> future = CompletableFuture.runAsync(() -> { System.out.println("Executing an asynchronous action..."); try { Thread.sleep(1500); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } System.out.println("Action done!"); }); // Waiting with get(), which can throw exceptions that occurred during execution future.get(); System.out.println("Asynchronous procedure concluded successfully."); } } ``` 这两种方式都可以有效地使程序等待由 `runAsync` 发起的操作完成后再继续执行其他逻辑。选择哪种取决于具体的应用场景以及对异常处理的需求。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值