Java的ThreadPoolExecutor业务线程池详细解析

 更新时间:2024年01月18日 10:39:15   作者:岸河  
这篇文章主要介绍了Java线程池ThreadPoolExecutor详细解析,任务刚开始进来的时候就创建核心线程,核心线程满了会把任务放到阻塞队列,阻塞队列满了之后才会创建空闲线程,达到最大线程数之后,再有任务进来,就只能执行拒绝策略了,需要的朋友可以参考下

ThreadPoolExecutor业务线程池

1.什么是业务线程池?

在业务开发中,用来处理业务的线程池。

2.为什么需要业务线程池?

大多数同学都是做业务开发的,很多业务的操作并非要求一定是同步的。例如,对于一系列连续的业务逻辑处理,很多都是数据的组装,拼接,查询,或者将数据同步给各个下层业务(对事务性没有严格要求);或者对数据的批量操作;这些都可以是异步的。通常业务项目使用的都是的servlet框架,都是使用一个线程进行业务逻辑处理,这种模型是通用的,但不一定是最佳的,不一定是最适合的。需要我们业务开发者根据实际的业务场景去灵活应用,达到最快的响应,最大的吞吐量。

3.业务线程池应用的思路是来自哪里?

个人理解,来自于开源框架。各种池化的概念,太多了,线程池,内存池,实例池,连接池。太多框架使用了线程池的概念,spring,tomcat,dubbo,netty,rocketmq,nacos,druid,总而言之,几乎所有的框架,都用到了线程池。虽然他们是框架线程池,但是抽出来想一下,对于框架线程池来讲,我们对于框架的使用,也是业务流程,也需要业务逻辑的处理,因此,业务线程池,框架线程池,两者并无区别。

一、业务线程池的好处

这里借用《Java 并发编程的艺术》提到的来说一下使用线程池的好处:

  • 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
  • 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

二、线程池基本认识

参数说明

/**
 * 用给定的初始参数创建一个新的ThreadPoolExecutor。
 */
public ThreadPoolExecutor(int corePoolSize,//线程池的核心线程数量
                          int maximumPoolSize,//线程池的最大线程数
                          long keepAliveTime,//当线程数大于核心线程数时,多余的空闲线程存活的最长时间
                          TimeUnit unit,//时间单位
                          BlockingQueue<Runnable> workQueue,//任务队列,用来储存等待执行任务的队列
                          ThreadFactory threadFactory,//线程工厂,用来创建线程,一般默认即可
                          RejectedExecutionHandler handler//拒绝策略,当提交的任务过多而不能及时处理时,我们可以定制策略来处理任务
                           ) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

拒绝策略

  • AbortPolicy:直接抛出异常,这是默认策略;
  • CallerRunsPolicy:用调用者所在的线程来执行任务;(这种场景下,可以保证数据不丢失,但是会阻塞主线程)
  • DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
  • DiscardPolicy:直接丢弃任务;

ExecutorService 中 shutdown()、shutdownNow()、awaitTermination() 含义和区别

  • shutdown():停止接收新任务,原来的任务继续执行
  • shutdownNow():停止接收新任务,原来的任务停止执行
  • awaitTermination(long timeOut, TimeUnit unit):当前线程阻塞

注意:

awaitTermination一般是配合shutdown使用。

ThreadPoolExecutor运行状态

ThreadPoolExecutor类中定义了5个Integer常量,状态分别为

// runState is stored in the high-order bits
    private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;

在这里插入图片描述

经典面试题

线程池什么时候创建核心线程,什么时候把任务放进阻塞队列,什么时候创建空闲线程?

答:任务刚开始进来的时候就创建核心线程,核心线程满了会把任务放到阻塞队列,阻塞队列满了之后才会创建空闲线程,达到最大线程数之后,再有任务进来,就只能执行拒绝策略了。

注意,执行拒绝策略有两个场景,一个是空闲线程也满了,二是线程池不在运行了,比如执行了shutdown的方法,但是这个时候又来了新任务。

在这里插入图片描述

基础知识

现阻塞队列的接口是BlockingQueue,jdk1.5新增的,在juc包下面,作者是Doug Lea,它的父接口是Queue,也是jdk1.5新增的,在java.util包下面,属于集合类,作者还是Doug Lea。

三、线程池最佳实践

1.打印线程池的状态,关注线程池运行情况(个人非常喜欢)

/**
     * 打印线程池的状态
     *
     * @param threadPool 线程池对象
     */
    public static void printThreadPoolStatus(ThreadPoolExecutor threadPool) {
        ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(1, createThreadFactory("print-thread-pool-status", false));
        scheduledExecutorService.scheduleAtFixedRate(() -> {
            log.info("=========================");
            log.info("ThreadPool Size: [{}]", threadPool.getPoolSize());
            log.info("Active Threads: {}", threadPool.getActiveCount());
            log.info("Number of Tasks : {}", threadPool.getCompletedTaskCount());
            log.info("Number of Tasks in Queue: {}", threadPool.getQueue().size());
            log.info("=========================");
        }, 0, 1, TimeUnit.SECONDS);
    }

2.不同业务使用不同的业务线程池

父子任务也不要使用一个线程池(会发生死锁),死锁原因:父任务占用了所有的核心线程,自子任务在阻塞队列里等待父任务释放核心线程,父线程等待子任务完成任务。

3.为什么不能使用原生的Executors工具创建线程池

阻塞队列都是Integer.MAX,容易发生OOM,而且无线程池命名,没有关心空闲时间,拒绝策略,太粗糙了,除非你不关心业务。

4.如果设置线程数量?

有一个简单并且适用面比较广的公式:

  • CPU 密集型任务(N+1): 这种任务消耗的主要是 CPU 资源,可以将线程数设置为 N(CPU 核心数)+1,比 CPU 核心数多出来的一个线程是为了防止线程偶发的缺页中断,或者其它原因导致的任务暂停而带来的影响。一旦任务暂停,CPU 就会处于空闲状态,而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间。
  • I/O 密集型任务(2N): 这种任务应用起来,系统会用大部分的时间来处理 I/O 交互,而线程在处理 I/O 的时间段内不会占用 CPU 来处理,这时就可以将 CPU 交出给其它线程使用。因此在 I/O 密集型任务的应用中,我们可以多配置一些线程,具体的计算方法是 2N。

5.[美团] Java线程池实现原理及其在美团业务中的实践

由于队列设置过长,最大线程数设置失效,导致请求数量增加时,大量任务堆积在队列中,任务执行时间过长,最终导致下游服务的大量调用超时失败。

ThreadPoolExecutor的corePoolSize的值是可以设置的。利用这点加上配置中心,可以动态的调整核心线程数。

四、线程池总结

做好业务线程池,分三个级别

第一级别,根据业务特性实现不同的业务线程池。

第二级别,根据业务特性,动态调整线程池配置。

第三级别,实时监控与配置线程池运行情况。

到此这篇关于Java的ThreadPoolExecutor业务线程池详细解析的文章就介绍到这了,更多相关ThreadPoolExecutor业务线程池内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • java实现简单的加减乘除计算器

    java实现简单的加减乘除计算器

    这篇文章主要为大家详细介绍了java实现简单的加减乘除计算器,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-09-09
  • Java8中利用stream对map集合进行过滤的方法

    Java8中利用stream对map集合进行过滤的方法

    这篇文章主要给大家介绍了关于Java8中利用stream对map集合进行过滤的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2018-07-07
  • mybatis-plus雪花算法增强idworker的实现

    mybatis-plus雪花算法增强idworker的实现

    今天聊聊在mybatis-plus中引入分布式ID生成框架idworker,进一步增强实现生成分布式唯一ID,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-07-07
  • Java实现简单的邮件发送功能

    Java实现简单的邮件发送功能

    这篇文章主要为大家详细介绍了Java实现简单的邮件发送功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-07-07
  • Java传入用户名和密码并自动提交表单实现登录到其他系统的实例代码

    Java传入用户名和密码并自动提交表单实现登录到其他系统的实例代码

    这篇文章主要介绍了Java传入用户名和密码并自动提交表单实现登录到其他系统,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2017-01-01
  • MyBatis学习教程(三)-MyBatis配置优化

    MyBatis学习教程(三)-MyBatis配置优化

    这篇文章主要介绍了MyBatis学习教程(三)-MyBatis配置优化的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2016-05-05
  • Spring Boot整合 NoSQL 数据库 Redis详解

    Spring Boot整合 NoSQL 数据库 Redis详解

    这篇文章主要为大家介绍了Spring Boot整合 NoSQL 数据库 Redis详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-09-09
  • 解决Java导入excel大量数据出现内存溢出的问题

    解决Java导入excel大量数据出现内存溢出的问题

    今天小编就为大家分享一篇解决Java导入excel大量数据出现内存溢出的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-06-06
  • Java中的通用路径转义符介绍

    Java中的通用路径转义符介绍

    这篇文章主要介绍了Java中的通用路径转义符介绍,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • Java实现短信验证码的示例代码

    Java实现短信验证码的示例代码

    本文主要介绍了Java实现短信验证码的示例代码,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-03-03

最新评论