其他都不重要,就ExecutorService是主要的:
基本上分为单纯线程池和定时任务线程池:
说白了除了ForkJoinPool
所有都继承于ThreadPoolExecutor
或者是对ThreadPoolExecutor
的包装类:
//构造函数
public ThreadPoolExecutor(
int corePoolSize,//从0增加,直到维持不变的线程数
int maximumPoolSize,//最大创建的线程数,比corePoolSize多出来的是多余线程数,如果空闲会被释放
long keepAliveTime,//空闲多久释放
TimeUnit unit,//keepAliveTime的单位
BlockingQueue<Runnable> workQueue,//任务队列对象
ThreadFactory threadFactory,//线程生产工厂
RejectedExecutionHandler handler//压力大时如何处理拒绝任务
) {
}
上面构造方法的各个参数的默认值:
AbstractQueue
的子类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<Runnable> 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)
从上面输出可以解析,
最大10线程+最大队列10容量<21
,说明线程和队列都达到最大值后,根据拒绝策略处理。ThreadPoolExecutor线程池自身是线程安全的,但是对于执行的任务并不保证线程安全,也没有任何线程同步操作。需要用户自己处理线程安全。
以后看性能如何再改下面的猜测:
个人就基于并发峰值、任务平均处理时间等等,猜测创建线程池各个参数的合理区间:
平均并发数 | 高峰并发数 | 任务执行时间 | 合理参数配置: | 说明 |
---|---|---|---|---|
低 | 低 | 短 | 固定线程数=低合理值(CPU核心数) 最大线程数= 2倍CPU核心 空闲线程等待时间= 60s 队列容量= 较大合理值 拒绝策略= 队列不会满用不到 |
并发不高,不需要运行太多线程, 任务易处理,多出来全部放队列即可 |
低 | 低 | 很长 | 固定线程数=低合理值(CPU核心数) 最大线程数= 较大合理值 空闲线程等待时间= 60s 队列容量= 中等合理值 拒绝策略= 实际需要 |
并发不高,不需要运行太多线程, 任务时间长,尽可能利用线程数 |
低 | 极高 | 短 | 固定线程数=低合理值 最大线程数= 较高合理值 空闲线程等待时间= 60s 队列容量= 防止内存溢出较大值 拒绝策略= 还不满足,考虑买设备 |
平时并发不高,不需要运行太多线程, 任务易处理,并发高峰放队列和新线程 |
很高 | 极高 | 短 | 固定线程数=中等合理值 最大线程数= 较高合理值 空闲线程等待时间= 60s 队列容量= 防止内存溢出最大值 拒绝策略= 重要任务不抛弃,最大化利用内存和cpu资源 |
平时并发高,需要运行较多线程, 任务易处理,提升队列容量 |
很高 | 极高 | 很长 | 固定线程数=中等合理值 最大线程数= 较高合理值 空闲线程等待时间= 60s 队列容量= 防止内存溢出最大值 拒绝策略= 重要任务不抛弃,最大化利用内存和cpu资源 |
长时间任务,应该考虑另外使用任务调度容器来执行。 |
参数设置依据: