首页 > 编程语言 >JAVA线程池

JAVA线程池

时间:2024-02-05 18:47:05浏览次数:27  
标签:JAVA ThreadLocal 任务 线程 new 执行 ThreadPoolExecutor


线程池中的四种拒绝策略通常是指:

  1. AbortPolicy(默认策略):这是默认的拒绝策略。当线程池无法接受新任务时,它会抛出RejectedExecutionException异常。

  2. CallerRunsPolicy:在这种策略下,当线程池无法接受新任务时,会使用提交任务的线程来执行该任务。这样做的目的是为了降低新任务的提交速度,以便系统有时间处理现有的任务。

  3. DiscardPolicy:这种策略下,当线程池无法接受新任务时,会直接丢弃被拒绝的任务,不会给出任何提示或警告。

  4. DiscardOldestPolicy:在这种策略下,当线程池无法接受新任务时,会丢弃队列中等待时间最长的任务,并尝试将新任务添加到队列中。

线程池是 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 策略直接丢弃,要是很重要,可以考虑使用消息队列来替换线程池。

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

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

标签:JAVA,ThreadLocal,任务,线程,new,执行,ThreadPoolExecutor
From: https://www.cnblogs.com/huliangqing/p/18008626

相关文章

  • java 内存布局
    面试题:初始化创建的对象有多大?答案:16字节Objecta=newObject();16字节String[]b=newString[2]();16字节/24字节对象的内存布局主体内容包括:对象头:MarkWord、指针、数字长度(数组对象才有)数据体:对象的数据,初始化一般为空数据对齐:要求对象大小,为8的倍数,而......
  • java lambda 求分组内最大值
    可以使用lambda表达式,比较方便,这里主要想说下思路问题,之前一个时受到数据库的影响,一个是对api理解程度不够的原因,实现方式见方式一;后来有种恍然大悟的感觉,改成了方式二的实现;方式一:先分组,组内过滤每一条数据Map<String,List<UserLog>>collect=list.stream().collect(Collec......
  • C#多线程编程的Task(任务全面解析)
    原文链接:https://www.cnblogs.com/xietianjiao/p/7429742.htmlTask是.NET4.0加入的,跟线程池ThreadPool的功能类似,用Task开启新任务时,会从线程池中调用线程,而Thread每次实例化都会创建一个新的线程。 我们可以说Task是一种基于任务的编程模型。它与thread的主要区别是,它更加方便......
  • 32-Java中字符串、json、map之间的互相转换
    Java中字符串、json、map之间的互相转换 1.map转String、jsonObject对象packagemap;importjava.util.HashMap;importjava.util.Objects;importcom.alibaba.fastjson.JSON;importcom.alibaba.fastjson.JSONObject;publicclassMapDemo3{publicstatic......
  • 打开java语言世界通往字节码世界的大门——ASM字节码操作类库
    一、ASM介绍1、ASM是什么ASM是一个通用的Java字节码操作和分析框架。它可以用于修改现有类或直接以二进制形式动态生成类。ASM提供了一些常见的字节码转换和分析算法,可以从中构建定制的复杂转换和代码分析工具。ASM提供了与其他Java字节码框架类似的功能,但侧重于性能。由于它的......
  • Java 将PDF转为PowerPoint (2行代码)
    通过编程实现PDF转PPT的功能,可以自动化转换过程,减少手动操作的工作量,并根据需要进行批量转换。将PDF文件转换为PPT文档后,可以利用PPT的丰富功能和动画效果,达到更好的演示效果。在Java中,我们可以使用第三方库Spire.PDFforJava来将PDF转换为PowerPoint文档。以下示例包含将PDF转......
  • docker中调试java代码
    以shiro550为例子在vulhub/shiro/CVE-2016-4437启动环境docker-composeup-d然后看一下当前容器启动的命令是java-jar/shirodemo-1.0-SNAPSHOT.jar将容器内的jar包复制出来dockercp容器id:/shirodemo-1.0-SNAPSHOT.jar.然后ijidea新建项目,并且解压jar包到项......
  • JAVA之BigDecimal详解
    一、BigDecimal概述Java在java.math包中提供的API类BigDecimal,用来对超过16位有效位的数进行精确的运算。双精度浮点型变量double可以处理16位有效数,但在实际应用中,可能需要对更大或者更小的数进行运算和处理。一般情况下,对于那些不需要准确计算精度的数字,我们可以直接使用Float......
  • Java-08常用容器
    List接口:java.util.List<>。实现:java.util.ArrayList<>:变长数组java.util.LinkedList<>:双链表函数:add():在末尾添加一个元素clear():清空size():返回长度isEmpty():是否为空get(i):获取第i个元素set(i,val):将第i个元素设置为val栈类:java.util.Stack<>函......
  • Java-09异常处理
    tip:[start]异常处理可以允许我们在程序运行时进行诊断和补救。——闫学灿tip:[end]Error与Exception的区别Error是程序无法处理的错误,比如OutOfMemoryError、ThreadDeath等。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。此类异常是程序的致命异常,是无法捕获处理的。......