ThreadPoolExecutor

简介: 一 核心属性 1  int corePoolSize 指该线程池中核心线程数最大值 核心线程:线程池新建线程的时候,如果当前线程总数小于corePoolSize,则新建的是核心线程,如果超过corePoolSize,则新建的是非核心线程。


一 核心属性

1  int corePoolSize

指该线程池中核心线程数最大值

核心线程:线程池新建线程的时候,如果当前线程总数小于corePoolSize,则新建的是核心线程,如果超过corePoolSize,则新建的是非核心线程。核心线程默认情况下会一直存活在线程池中,即使这个核心线程啥也不干(闲置状态)。

如果指定ThreadPoolExecutor的allowCoreThreadTimeOut这个属性为true,那么核心线程如果不干活(闲置状态)的话,超过一定时间(时长下面参数决定),就会被销毁掉。

2  int maximumPoolSize

指该线程池中线程总数最大值。

线程总数 = 核心线程数 + 非核心线程数。

3  long keepAliveTime

指该线程池中非核心线程闲置超时时长

一个非核心线程,如果不干活(闲置状态)的时长超过这个参数所设定的时长,就会被销毁掉,如果设置allowCoreThreadTimeOut = true,则会作用于核心线程。

4  BlockingQueue workQueue

指该线程池中的任务队列。

当所有的核心线程都忙碌时,新添加的任务会被添加到这个队列中等待处理;如果队列满了,则新建非核心线程执行任务。

常用的workQueue类型:SynchronousQueue、LinkedBlockingQueue、ArrayBlockingQueue、DelayQueue。

1)   SynchronousQueue

这个队列接收到任务的时候,会直接提交给线程处理,而不保留它,如果所有线程都在忙碌,那就新建一个线程来处理这个任务。此队列通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。

此时需要注意线程数目与maximumPoolSize的关系,以及线程数目过多而消耗的服务器资源。

2)   LinkedBlockingQueue

这个队列接收到任务的时候,如果当前线程数小于核心线程数,则新建线程(核心线程)处理任务;如果当前线程数等于核心线程数,则进入队列等待。

由于这个队列没有最大值限制,即所有超过核心线程数的任务都将被添加到队列中,这也就导致了maximumPoolSize的设定失效,因为总线程数永远不会超过corePoolSize。

3)   ArrayBlockingQueue

可以限定队列的长度,接收到任务的时候,如果没有达到corePoolSize的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了maximumPoolSize,并且队列也满了,则发生错误。

4)   DelayQueue

队列内元素必须实现Delayed接口,这就意味着你传进去的任务必须先实现Delayed接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务

5  RejectedExecutionHandler handler

线程池无法处理任务时的丢弃策略。

1)   CallerRunsPolicy

拒绝这个任务,不在ThreadPoolExecutor线程池中的线程中运行,而是调用当前线程池的所在的线程去执行被拒绝的任务。

2)   AbortPolicy

ThreadPoolExecutor默认的拒绝策略,直接抛出异常。

3)   DiscardPolicy

线程池默默丢弃这个被拒绝的任务,不会抛出异常。

4)   DiscardOldestPolicy

会抛弃任务队列中最旧的任务(最先加入队列的任务),再把这个新任务添加到队列中去。

5)   总结

实际上查阅这四种ThreadPoolExecutor线程池自带的拒绝处理器实现,您可以发现CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy处理器针对被拒绝的任务并不是一个很好的处理方式。

CallerRunsPolicy在非线程池以外直接调用任务的run方法,可能会造成线程安全上的问题。

DiscardPolicy默默的忽略掉被拒绝任务,也没有输出日志或者提示,开发人员不会知道线程池的处理过程出现了错误;

DiscardOldestPolicy大部分情况下,默默地抛弃一部分任务都是一件很危险的事情。

通常使用AbortPolicy是最好的,因为抛弃任务时,开发者可以通过日志发现这个问题,并着手处理。

 

二 执行策略

通常当一个任务被添加进线程池时,总体的执行策略如下,不过具体会根据核心属性定义的值会有少许变动。

线程数量未达到corePoolSize,则新建一个线程(核心线程)执行任务

线程数量达到了corePools,则将任务移入队列等待。

队列已满,新建线程(非核心线程)执行任务

队列已满,总线程数又达到了maximumPoolSize,就会由(RejectedExecutionHandler)抛出异常

 

三 常见的线程池

1  CachedThreadPool

可缓存线程池

    ExecutorService mCachedThreadPool = Executors.newCachedThreadPool();

 

    public static ExecutorService newCachedThreadPool() {

        return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());

    }

2  FixedThreadPool

定长线程池


    ExecutorService mFixedThreadPool= Executors.newFixedThreadPool(int nThreads);
 
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

 

3  SingleThreadPool

只有一个线程的线程池。

    ExecutorService mSingleThreadPool = Executors.newSingleThreadPool();

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

4  ScheduledThreadPool

按照固定频率执行的线程池。

  

四 线程池配置

1  DB连接池配置

最小连接数=(平均QPS* QPS平均RT +平均TPS* TPS平均RT)/业务机器数

最大连接数=(峰值QPS* QPS平均RT +峰值TPS* TPS平均RT)/业务机器数;如果业务代码中没有另起多线程,那么也可以使用公式:最大连接数= 容器处理请求的线程池大小

另外,如果需要,也可以在此公式的基础上预留一些buffer。

2  Java线程池配置

1)   CPU密集型任务的线程池配置

因为cpu执行速度非常快,往往切换线程上下文环境所耗费的时间比执行代码花费的时间更长,所以CPU密集型任务的线程数应该尽量保持和CPU核数一致,以减少线程切换带来的性能损耗。

2)   IO密集型任务的线程池配置

IO密集型任务的主要性能瓶颈在于等待IO结果,当遇到任务中存在从DB中读取数据,从缓存中读取数据时,就可以认为是IO密集型任务。一般而言业务系统配置线程池基本上都会采用此模型配置。

假设:an表示系统平均每秒收到的请求数;mn表示系统每秒收到的峰值请求数;at表示每个任务执行的平均时间;more表示预留的buffer;n表示线程池个数;那么线程池可以参考一下公式配置:

核心线程数 = an * at /n * (1+more%)

最大线程数 = mn * at /n * (1+more%)

 

 

 

相关文章
|
分布式计算 监控 搜索推荐
Elasticsearch之SearchScroll原理剖析和优化
Elasticsearch是一款优秀的开源企业级搜索引擎,其查询接口主要为Search接口,提供了丰富的各类查询、排序、统计聚合等功能。本文将要介绍的是另一个查询接口SearchScroll,同时介绍一下我们在这方面做的一些性能和稳定性等方面的优化工作。   Elasticsearch的SearchScroll接口可用于从索引中检索大量数据,或者是所有的数据,值得注意的是Elasti
5918 0
Elasticsearch之SearchScroll原理剖析和优化
Debian 官方源换为国内的源的操作方法
apt-get update 报错,采用更换源的方式解决问题。
60756 0
|
11月前
|
人工智能 Rust 运维
这个神器让你白嫖ClaudeOpus 4.5,Gemini 3!还能接Claude Code等任意平台
加我进AI讨论学习群,公众号右下角“联系方式”文末有老金的 开源知识库地址·全免费
11321 22
|
7月前
|
自然语言处理 数据挖掘 调度
《一套可复制的ClawHub专属工作流搭建完整指南》
本文纠正了多数人零散使用ClawHub技能的普遍误区,指出其核心价值并非单个工具的能力,而是作为生产力编排平台实现技能自由组合。作者基于两个月的深度实测与二十多个专属工作流的搭建经验,系统分享了任务原子化拆分、技能专一性匹配、统一中间数据格式、主从架构调度等核心方法,并以每日行业早报自动化工作流为例展示落地效果。文章最终提出,技能组合的终极意义是将个人经验固化为可重复执行的流程,实现生产力的指数级提升。
325 4
|
12月前
|
缓存 Java Spring
JDK-CGLIB-反射
JDK动态代理基于接口,利用反射和Proxy实现;CGLIB通过ASM生成子类,支持无接口类代理,但无法代理final方法。二者性能随JDK版本变化,JDK8后差距缩小。Spring AOP根据是否有接口自动选择代理方式。Java反射机制则允许运行时获取类信息并动态操作对象,是动态代理的基础。
347 0
|
存储 缓存 NoSQL
如何解决缓存击穿?
缓存击穿是指热点数据失效时大量请求直接冲击数据库,可能导致系统崩溃。解决方案包括:永不过期策略避免缓存失效瞬间的穿透;互斥锁控制并发访问;热点预热提前刷新缓存;熔断降级在数据库压力大时返回默认值;二级缓存降低Redis压力。实际中常组合使用多种方案,如热点预热+互斥锁+熔断降级,以提升系统稳定性与性能。
1613 0
|
弹性计算
阿里云ECS云服务器8核16G配置收费价格,多种ECS实例CPU及费用清单
阿里云8核16G云服务器价格因实例类型而异。计算型c9i约743元/月,一年6450元(7折);通用算力型u1仅673元/月,一年4225元(5.1折)。实际价格享时长折扣,详情见ECS官网。
|
12月前
|
人工智能 自然语言处理 搜索推荐
2025年汽车行业AIGC供应商推荐
2025年智能出行浪潮下,AIGC视频供应商加速汽车行业智能化转型。集之互动、可灵、即梦凭借全栈式解决方案、数据驱动与AI交互优化,助力车企降本增效,提升用户体验与合规安全,推动产业创新发展。
322 1
|
存储
计算机组成原理(7)----CPU内部单总线数据通路
计算机组成原理(7)----CPU内部单总线数据通路
2525 0

热门文章

最新文章