首页 > 其他分享 >【踩坑指南】线程池使用不当的五个坑

【踩坑指南】线程池使用不当的五个坑

时间:2024-02-05 09:44:53浏览次数:20  
标签:指南 使用不当 ThreadLocal 任务 线程 new 执行 ThreadPoolExecutor

线程池是 Java 多线程编程中的一个重要概念,它可以有效地管理和复用线程资源,提高系统的性能和稳定性。但是线程池的使用也有一些注意事项和常见的错误,如果不小心,就可能会导致一些严重的问题,比如内存泄漏、死锁、性能下降等。

本文将介绍线程池使用不当的五个坑,以及如何避免和解决它们,大纲如下,

image

坑一:线程池中异常消失

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

问题原因

@Test
public 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;
    });
}

如上代码,在线程池执行任务时,没有添加异常处理。导致任务内部发生异常时,内部错误无法被记录下来。

解决方法

在线程池执行任务方法内添加 try/catch 处理,代码如下,

@Test
public 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 异常处理方法。

这样就可以做到全局处理异常了,代码如下,

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 方法是无法处理的。

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

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

问题原因

曾经有一个线上业务接口使用了线程池进行第三方接口调用,线程池配置里的拒绝策略采用的是 CallerRunsPolicy。示例代码如下,

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

threadPoolExecutor.execute(() -> {
    // 调用第三方接口
    ...
});

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

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

解决方法

首先我们要考虑业务接口的可用性,就算线程池任务被丢弃,也不应该影响业务接口。

在业务接口稳定性得到保证的情况下,在考虑到线程池任务的重要性,不是很重要的话,可以使用 DiscardPolicy 策略直接丢弃,要是很重要,可以考虑使用消息队列来替换线程池。

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

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

标签:指南,使用不当,ThreadLocal,任务,线程,new,执行,ThreadPoolExecutor
From: https://www.cnblogs.com/waynaqua/p/18007416

相关文章

  • 一个线程,从“生”到“死”经历的过程
    本文分享自华为云社区《面试必问|一个线程从创建到消亡要经历哪些阶段?》,作者:冰河。今天,我们就结合 操作系统线程和编程语言线程 再次深入探讨线程的生命周期问题,线程的生命周期其实没有我们想象的那么简单!!理解线程的生命周期本质上理解了生命周期中各个节点的状态转换机制......
  • Nacos安装指南
    Nacos安装指南1.Windows安装开发阶段采用单机安装即可。1.1.下载安装包在Nacos的GitHub页面,提供有下载链接,可以下载编译好的Nacos服务端或者源代码:GitHub主页:https://github.com/alibaba/nacosGitHub的Release下载页:https://github.com/alibaba/nacos/releases如图:本课程......
  • java----多线程
    1.什么是线程和进程?进程好比一个软件,线程好比软件中的一个功能。一个进程包含了多个线程,举例:比如360软件中木马查杀;买票的时候,火车站就是一个进程,各个窗口表示线程。并行与并发之间的区别:好比做饭吧,几个厨师分别同时做不同的食物-------------------并行---------------......
  • Karpenter 实战指南
    介绍Karpenter是一个用于Kubernetes集群的弹性伸缩工具,能够自动调整节点数量,适用于多种场景。本指南将详细介绍如何在实际应用中安装、配置和使用Karpenter,并探讨一些最佳实践。步骤1:安装Karpenter使用Helm安装Karpenter:helmrepoaddkarpenterhttps://awslabs.github......
  • 【Java基础】Java线程的六种状态详解
    NEW状态当创建一个Thread对象但尚未调用其start()方法时,线程处于NEW状态。在这个状态下,线程并未启动,仅完成了初始化阶段。RUNNABLE状态RUNNABLE是Java中较为特殊的一个状态,它涵盖了传统操作系统中的就绪和运行两种状态。当线程已启动且CPU调度器为其分配了时间片或线程正在等待系......
  • yarn安装太慢,如何多线程安装依赖
    Yarn本身设计时就考虑到了并行安装依赖以提高速度,它默认使用多线程来下载和安装包。当执行yarninstall时,Yarn会利用所有可用的CPU核心,并通过其内部的并行化机制来加速安装过程。如果你发现Yarn在安装依赖时仍然显得较慢,可以尝试以下方法来优化:启用网络代理:如果你的网......
  • threadlocal 线程本地变量,线程独享
         ......
  • C++多线程 第三章 在线程间共享数据
    第三章在线程间共享数据共享数据基本问题如果所有共享数据都只读,那就没有问题.不变量(invariants):对特定数据结构总为真的语句.例如:"该变量表示线程数量."修改线程之间共享数据的一个常见潜在问题就是破坏不变量.竞争条件(racecondition):线程竞争执行各自的操作,导......
  • 深入浅出Java多线程(七):重排序与Happens-Before
    引言大家好,我是你们的老伙计秀才!今天带来的是[深入浅出Java多线程]系列的第七篇内容:重排序与Happens-Before。大家觉得有用请点赞,喜欢请关注!秀才在此谢过大家了!!!在上一篇文章中,我们简单提了一下重排序与Happens-Before。在这篇文章中我们将深入讲解一下重排序与Happens-Before,然......
  • Sklearn、TensorFlow 与 Keras 机器学习实用指南第三版(七)
    原文:Hands-OnMachineLearningwithScikit-Learn,Keras,andTensorFlow译者:飞龙协议:CCBY-NC-SA4.0第十六章:使用RNN和注意力进行自然语言处理当艾伦·图灵在1950年想象他著名的Turing测试时,他提出了一种评估机器匹配人类智能能力的方法。他本可以测试许多事情,比如......