多线程,线程池Executor的接口类图:
其他都不重要,就ExecutorService是主要的:
基本上分为单纯线程池和定时任务线程池:
说白了除了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提供的四种创建线程池的参数配置,都是特别对应不同场景的较好设置值,值得参考。