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业务线程池内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 解决Feign配置RequestContextHolder.getRequestAttributes()为null的问题

    解决Feign配置RequestContextHolder.getRequestAttributes()为null的问题

    这篇文章主要介绍了解决Feign配置RequestContextHolder.getRequestAttributes()为null的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-01-01
  • mvn compile报错“程序包com.XXX不存在”

    mvn compile报错“程序包com.XXX不存在”

    本文主要介绍了mvn compile报错“程序包com.XXX不存在”,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-01-01
  • Spring通过ApplicationContext主动获取bean的方法讲解

    Spring通过ApplicationContext主动获取bean的方法讲解

    今天小编就为大家分享一篇关于Spring通过ApplicationContext主动获取bean的方法讲解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-03-03
  • 解决Maven本地仓库明明有对应的jar包但还是报找不到的问题

    解决Maven本地仓库明明有对应的jar包但还是报找不到的问题

    这篇文章主要介绍了解决Maven本地仓库明明有对应的jar包但还是报找不到的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-10-10
  • mybatis-plus配置拦截器实现sql完整打印的代码设计

    mybatis-plus配置拦截器实现sql完整打印的代码设计

    在使用mybatis-plus(mybatis)的时候,往往需要打印完整的sql语句,然而输出的日志不是很理想,因为sql语句中的关键字段信息都是用?来代替的,所以本文分享了一下自己写了一个拦截器实现了sql完整的打印,需要的朋友可以参考下
    2024-06-06
  • 初探Spring Cloud Gateway实战

    初探Spring Cloud Gateway实战

    这篇文章主要介绍了创建网关项目(Spring Cloud Gateway)过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2021-08-08
  • java实现数字猜拳小游戏

    java实现数字猜拳小游戏

    这篇文章主要为大家详细介绍了java实现数字猜拳的小游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-06-06
  • Spring运行时手动注入bean的方法实例

    Spring运行时手动注入bean的方法实例

    spring给我们提供了IOC服务,让我们可以用注解的方式,方便的使用bean的相互引用,下面这篇文章主要给大家介绍了关于Spring运行时手动注入bean的相关资料,需要的朋友可以参考下
    2022-05-05
  • MyBatis-Plus详解(环境搭建、关联操作)

    MyBatis-Plus详解(环境搭建、关联操作)

    MyBatis-Plus 是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生,今天通过本文给大家介绍MyBatis-Plus环境搭建及关联操作,需要的朋友参考下吧
    2022-09-09
  • IDEA .iml文件及.idea文件夹超详细讲解

    IDEA .iml文件及.idea文件夹超详细讲解

    iml文件是IntelliJ IDEA自己创建的模块文件,用于java应用开发,存储一些模块相关的信息,比如一个Java组建,插件组建,Maven组建等,这篇文章主要介绍了IDEA .iml文件及.idea文件夹,需要的朋友可以参考下
    2023-09-09

最新评论