java util下的并发包_jdk并发包下:使用java.util.concurrent.Executor线程池

多线程,线程池Executor的接口类图:

f5925f63b30515ccd3a1fd19846debe6.png

其他都不重要,就ExecutorService是主要的:

3f3817fc7ca6573fb10c3977701075cc.png

基本上分为单纯线程池和定时任务线程池:

eb54c975a215e2b0df575a91f83ff863.png

说白了除了ForkJoinPool所有都继承于ThreadPoolExecutor或者是对ThreadPoolExecutor的包装类:

//构造函数

public ThreadPoolExecutor(

int corePoolSize,//从0增加,直到维持不变的线程数

int maximumPoolSize,//最大创建的线程数,比corePoolSize多出来的是多余线程数,如果空闲会被释放

long keepAliveTime,//空闲多久释放

TimeUnit unit,//keepAliveTime的单位

BlockingQueue workQueue,//任务队列对象

ThreadFactory threadFactory,//线程生产工厂

RejectedExecutionHandler handler//压力大时如何处理拒绝任务

) {

}

上面构造方法的各个参数的默认值:

corePoolSize:一般是1或者cpu核心数或者其他定量

maximumPoolSize:一般是0或者无限大

keepAliveTime:一般是0或者60s

unit:一般是TimeUnit.SECONDS

workQueue:一般是AbstractQueue 的子类

threadFactory:一般是DefaultThreadFactory implements ThreadFactory实例

Executors提供的类实例化方法:

int corePoolSize = 4;

ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(corePoolSize);

ExecutorService executorService1 = Executors.newSingleThreadExecutor();

ExecutorService executorService2 = Executors.newFixedThreadPool(corePoolSize);

ExecutorService executorService4 = Executors.newCachedThreadPool();

ExecutorService executorService3 = Executors.newWorkStealingPool(corePoolSize);

它们主要区别就是实例化的参数不同,比如固定的线程数是多少,并发高峰时的线程数能达到多少,空闲线程的存活时间不同,使用的保存任务的队列类不同,繁忙时多余任务的拒绝策略,等等。

需要根据实际并发需求和特点来选择不同的实例,或者自己实例化ThreadPoolExecutor直接使用。

创建好线程池后的调用就是submit(异步返回结果)和execute(不返回结果),其他方法 也简单不说了。

说下最关心的ThreadPoolExecutor的参数对性能的影响:

过程如下:

当线程池初始化完成之后,而且当前线程数量小于corePoolSize,新来的任务直接通过创建线程来直接运行,并且线程运行后不会销毁。

当线程池中正在运行的线程达到 corePoolSize 个时,不会继续创建线程,任务会放到 taskQueue 中排队等候;

当 taskQueue(阻塞队列)的容量达到上限(即队列中不能再加入任务线程了),而且设置的maximumPoolSize大于corePoolSize时,则新增额外线程来处理任务;

当 taskQueue 的容量达到上限,且 当前线程数poolSize 达到maximumPoolSize,那么线程池已经达到极限,会根据饱和策略RejectedExecutionHandler处理新的任务。

测试验证:

我设置核心线程数为4个,最大线程数为10个,队列为10个满容量,拒绝策略为抛异常:

int corePoolSize = 4;

int maximumPoolSize = 10;

long keepAliveTime = 10L;

TimeUnit unit = TimeUnit.SECONDS;

BlockingQueue workQueue = new LinkedBlockingQueue<>(10);

ThreadFactory threadFactory = Executors.defaultThreadFactory();

RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize,

maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);

,getActiveCount=0 ,getPoolSize=0 ,getTaskCount=0 ,getLargestPoolSize=0 ,getQueueSize=0

,getActiveCount=1 ,getPoolSize=1 ,getTaskCount=1 ,getLargestPoolSize=1 ,getQueueSize=0

,getActiveCount=2 ,getPoolSize=2 ,getTaskCount=2 ,getLargestPoolSize=2 ,getQueueSize=0

,getActiveCount=3 ,getPoolSize=3 ,getTaskCount=3 ,getLargestPoolSize=3 ,getQueueSize=0

,getActiveCount=4 ,getPoolSize=4 ,getTaskCount=4 ,getLargestPoolSize=4 ,getQueueSize=0

,getActiveCount=4 ,getPoolSize=4 ,getTaskCount=5 ,getLargestPoolSize=4 ,getQueueSize=1

,getActiveCount=4 ,getPoolSize=4 ,getTaskCount=6 ,getLargestPoolSize=4 ,getQueueSize=2

,getActiveCount=4 ,getPoolSize=4 ,getTaskCount=7 ,getLargestPoolSize=4 ,getQueueSize=3

,getActiveCount=4 ,getPoolSize=4 ,getTaskCount=8 ,getLargestPoolSize=4 ,getQueueSize=4

,getActiveCount=4 ,getPoolSize=4 ,getTaskCount=9 ,getLargestPoolSize=4 ,getQueueSize=5

,getActiveCount=4 ,getPoolSize=4 ,getTaskCount=10 ,getLargestPoolSize=4 ,getQueueSize=6

,getActiveCount=4 ,getPoolSize=4 ,getTaskCount=11 ,getLargestPoolSize=4 ,getQueueSize=7

,getActiveCount=4 ,getPoolSize=4 ,getTaskCount=12 ,getLargestPoolSize=4 ,getQueueSize=8

,getActiveCount=4 ,getPoolSize=4 ,getTaskCount=13 ,getLargestPoolSize=4 ,getQueueSize=9

,getActiveCount=4 ,getPoolSize=4 ,getTaskCount=14 ,getLargestPoolSize=4 ,getQueueSize=10

,getActiveCount=5 ,getPoolSize=5 ,getTaskCount=15 ,getLargestPoolSize=5 ,getQueueSize=10

,getActiveCount=6 ,getPoolSize=6 ,getTaskCount=16 ,getLargestPoolSize=6 ,getQueueSize=10

,getActiveCount=7 ,getPoolSize=7 ,getTaskCount=17 ,getLargestPoolSize=7 ,getQueueSize=10

,getActiveCount=8 ,getPoolSize=8 ,getTaskCount=18 ,getLargestPoolSize=8 ,getQueueSize=10

,getActiveCount=9 ,getPoolSize=9 ,getTaskCount=19 ,getLargestPoolSize=9 ,getQueueSize=10

,getActiveCount=10 ,getPoolSize=10 ,getTaskCount=20 ,getLargestPoolSize=10 ,getQueueSize=10

Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.exemple.Test6$task@63947c6b rejected from java.util.concurrent.ThreadPoolExecutor@2b193f2d[Running, pool size = 10, active threads = 10, queued tasks = 10, completed tasks = 0]

at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)

at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)

at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)

at com.exemple.Test6.main(Test6.java:30)

从上面输出可以解析,

前4个任务,线程数一直增长从1到4,说明小于corePoolSize时一直创建线程

5到14个任务,线程数不变,队列添加从1到10,说明大于corePoolSize时加入队列等待

14到20个任务,队列不变,线程数又从4增长到10,说明队列满时又创建线程直到达到最大线程数

第21个任务,抛出异常,因为最大10线程+最大队列10容量<21,说明线程和队列都达到最大值后,根据拒绝策略处理。

ThreadPoolExecutor线程池自身是线程安全的,但是对于执行的任务并不保证线程安全,也没有任何线程同步操作。需要用户自己处理线程安全。

以后看性能如何再改下面的猜测:

个人就基于并发峰值、任务平均处理时间等等,猜测创建线程池各个参数的合理区间:

平均并发数

高峰并发数

任务执行时间

合理参数配置:

说明

固定线程数=低合理值(CPU核心数)

最大线程数=2倍CPU核心

空闲线程等待时间=60s

队列容量=较大合理值

拒绝策略=队列不会满用不到

并发不高,不需要运行太多线程,

任务易处理,多出来全部放队列即可

很长

固定线程数=低合理值(CPU核心数)

最大线程数=较大合理值

空闲线程等待时间=60s

队列容量=中等合理值

拒绝策略=实际需要

并发不高,不需要运行太多线程,

任务时间长,尽可能利用线程数

极高

固定线程数=低合理值

最大线程数=较高合理值

空闲线程等待时间=60s

队列容量=防止内存溢出较大值

拒绝策略=还不满足,考虑买设备

平时并发不高,不需要运行太多线程,

任务易处理,并发高峰放队列和新线程

很高

极高

固定线程数=中等合理值

最大线程数=较高合理值

空闲线程等待时间=60s

队列容量=防止内存溢出最大值

拒绝策略=重要任务不抛弃,最大化利用内存和cpu资源

平时并发高,需要运行较多线程,

任务易处理,提升队列容量

很高

极高

很长

固定线程数=中等合理值

最大线程数=较高合理值

空闲线程等待时间=60s

队列容量=防止内存溢出最大值

拒绝策略=重要任务不抛弃,最大化利用内存和cpu资源

长时间任务,应该考虑另外使用任务调度容器来执行。

参数设置依据:

平常并发操作一般的话,线程数不是越高越好,相对于物理真实的线程数和线程时间片时间长度,此参数设置应该合理区间,不然实质上会经常切换线程调度,消耗切换时间和资源。

为了提高线程池处理能力,如果设置队列容量过大,当真的有大批任务过来,可能导致内存溢出。而且队列过大,就不再触发最大线程数这个设置,一直都是固定线程处理任务。

Executors提供的四种创建线程池的参数配置,都是特别对应不同场景的较好设置值,值得参考。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值