【踩坑指南】线程池使用不当的五个坑
2024-02-04 09:00:55 软件 176观看
摘要线程池是 Java 多线程编程中的一个重要概念,它可以有效地管理和复用线程资源,提高系统的性能和稳定性。但是线程池的使用也有一些注意事项和常见的错误,如果不小心,就可能会导致一些严重的问题,比如内存泄漏、死锁、性能下

线程池是 Java 多线程编程中的一个重要概念,它可以有效地管理和复用线程资源,提高系统的性能和稳定性。但是线程池的使用也有一些注意事项和常见的错误,如果不小心,就可能会导致一些严重的问题,比如内存泄漏、死锁、性能下降等。最后文末还有免费红包封面可以领取,回馈给各位读者朋友。ulI28资讯网——每日最新资讯28at.com

本文将介绍线程池使用不当的五个坑,以及如何避免和解决它们,大纲如下,ulI28资讯网——每日最新资讯28at.com

图片ulI28资讯网——每日最新资讯28at.com

坑一:线程池中异常消失

线程池执行方法时要添加异常处理,这是一个老生常谈的问题,可是直到最近我都有同事还在犯这个错误,所以我还是要讲一下,不过我还提到了一种优雅的线程池全局异常处理的方法,大家可以往下看。ulI28资讯网——每日最新资讯28at.com

问题原因

@Testpublic void test() throws Exception {    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(        5,         10,         60,        TimeUnit.SECONDS,         new ArrayBlockingQueue<>(100000));    Future<Integer> submit = threadPoolExecutor.execute(() -> {        int i = 1 / 0; // 发生异常        return i;    });}

如上代码,在线程池执行任务时,没有添加异常处理。导致任务内部发生异常时,内部错误无法被记录下来。ulI28资讯网——每日最新资讯28at.com

解决方法

在线程池执行任务方法内添加 try/catch 处理,代码如下,ulI28资讯网——每日最新资讯28at.com

@Testpublic void test() throws Exception {    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(        5,         10,         60,        TimeUnit.SECONDS,         new ArrayBlockingQueue<>(100000));    Future<Integer> submit = threadPoolExecutor.execute(() -> {        try {            int i = 1 / 0;            return i;        } catch (Exception e) {            log.error(e.getMessage(), e);            return null;        }    });}

优雅的进行线程池异常处理

当线程池调用任务方法很多时,那么每个线程池任务执行的方法内都要添加 try/catch 处理,这就不优雅了,其实 ThreadPoolExecutor 线程池类支持传入 ThreadFactory 参数用于自定义线程工厂,这样我们在创建线程时,就可以指定 setUncaughtExceptionHandler 异常处理方法。ulI28资讯网——每日最新资讯28at.com

这样就可以做到全局处理异常了,代码如下,ulI28资讯网——每日最新资讯28at.com

ThreadFactory threadFactory = r -> {    Thread thread = new Thread(r);    thread.setUncaughtExceptionHandler((t, e) -> {        // 记录线程异常        log.error(e.getMessage(), e);    });    return thread;};ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(    5,     10,     60,    TimeUnit.SECONDS,     new ArrayBlockingQueue<>(100000));threadPoolExecutor.execute(() -> {    log.info("---------------------");    int i = 1 / 0;});

不过要注意的是上面 setUncaughtExceptionHandler 方法只能针对线程池的 execute 方法来全局处理异常。对于线程池的 submit 方法是无法处理的。ulI28资讯网——每日最新资讯28at.com

坑二:拒绝策略设置错误导致接口超时

在 Java 中,线程池拒绝策略可以说一个常见八股文问题。大家虽然都记住了线程池有四种决绝策略,可是实际代码编写中,我发现大多数人都只会用 CallerRunsPolicy 策略(由调用线程处理任务)。我吃过这个亏,因此也拿出来讲讲。ulI28资讯网——每日最新资讯28at.com

问题原因

曾经有一个线上业务接口使用了线程池进行第三方接口调用,线程池配置里的拒绝策略采用的是 CallerRunsPolicy。示例代码如下,ulI28资讯网——每日最新资讯28at.com

// 某个线上线程池配置如下ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(        50, // 最小核心线程数        50, // 最大线程数,当队列满时,能创建的最大线程数        60L, TimeUnit.SECONDS, // 空闲线程超过核心线程时,回收该线程的最大等待时间        new LinkedBlockingQueue<>(5000), // 阻塞队列大小,当核心线程使用满时,新的线程会放进队列        new CustomizableThreadFactory("task"), // 自定义线程名        new ThreadPoolExecutor.CallerRunsPolicy() // 线程执行的拒绝策略);threadPoolExecutor.execute(() -> {    // 调用第三方接口    ...});

在第三方接口异常的情况下,线程池任务调用第三方接口一直超时,导致核心线程数、最大线程数堆积被占满、阻塞队列也被占满的情况下,也就会执行拒绝策略,但是由于使用的是 CallerRunsPolicy 策略,导致线程任务直接由我们的业务线程来执行。ulI28资讯网——每日最新资讯28at.com

因为第三方接口异常,所以业务线程执行也会继继续超时,线上服务采用的 Tomcat 容器,最终也就导致 Tomcat 的最大线程数也被占满,进而无法继续向外提供服务。ulI28资讯网——每日最新资讯28at.com

解决方法

首先我们要考虑业务接口的可用性,就算线程池任务被丢弃,也不应该影响业务接口。ulI28资讯网——每日最新资讯28at.com

在业务接口稳定性得到保证的情况下,在考虑到线程池任务的重要性,不是很重要的话,可以使用 DiscardPolicy 策略直接丢弃,要是很重要,可以考虑使用消息队列来替换线程池。ulI28资讯网——每日最新资讯28at.com

坑三:重复创建线程池导致内存溢出

不知道大家有没有犯过这个问题,不过我确实犯过,归根结底还是写代码前,没有思考好业务逻辑,直接动手,写一步算一步

本文链接:http://www.28at.com/showinfo-26-72429-0.html【踩坑指南】线程池使用不当的五个坑

声明:本网页内容旨在传播知识,不代表本站观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。

显示全文

上一篇:深入了解Java 8 新特性-日期时间API之LocalDateTime类

下一篇:什么是数据同步利器DataX,如何使用?

最新热点