一、线程池作用
线程池能够对线程进行统一分配,调优和监控:
降低资源消耗(线程无限制地创建,然后使用完毕后销毁)
提高响应速度(无须创建线程)
提高线程的可管理性
二、原理
线程集合workSet和阻塞队列workQueue,新添加的线程放入阻塞队列,workerSet中的一个个线程worker会从阻塞队列中拿线程来执行,如果阻塞队列中没有线程,worker会阻塞。
worker没达到核心线程数之前,来一个任务创一个worker,也就是线程
三、七大参数
1、构造
public ThreadPoolExecutor(int corePoolSize, //核心线程数
int maximumPoolSize, //最大线程数
long keepAliveTime, //超过核心线程数以外的线程的允许空闲时间
TimeUnit unit, //空闲时间单位
BlockingQueue<Runnable> workQueue, //阻塞队列对象
ThreadFactory threadFactory, //线程工厂,给线程起名字用的,没啥用
RejectedExecutionHandler handler) //线程数达到最大核心线程数,且阻塞队列已满时的拒绝策略
2、参数设置原则
从任务的优先级,任务的执行时间长短,任务的性质(CPU密集/ IO密集),任务的依赖关系这四个角度来分析。
并且近可能地使用有界的工作队列。
性质不同的任务可用使用不同规模的线程池分开处理:
CPU密集型: 尽可能少的线程,Ncpu+1
IO密集型: 尽可能多的线程, Ncpu*2,比如数据库连接池
混合型: CPU密集型的任务与IO密集型任务的执行时间差别较小,拆分为两个线程池;否则没有必要拆分。
四、实用的三种线程池
1、newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
可以看到,核心线程数和最大线程数一致,那允许空闲时间就失效了,因为不会有超过核心线程数的线程存在
没有设置拒绝策略,默认为AbortPolicy,会抛出RejectedExecutionException
但即便设置了拒绝策略也无效,因为阻塞队列是无界的,最大Integer.MAX_VALUE,不会满的
2、newSingleTreadPool
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
可以看到,只有一个线程在运行,同样keepalivetime和拒绝策略失效
3、newCachedTreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
可以看到,没有核心线程数,一旦线程空闲超过60秒,就会停止,下次再建新的线程,会导致一定的系统开销
五、应该自己new线程池
通过参数看到,上面三个常用的线程池,都会造成OOM问题,要么阻塞队列无限长,要么线程数无限多
所以自己new线程池比较适合自己的业务
new ThreadPoolExecutor(5, 200, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
六、ScheduledThreadPoolExecutor
继承自ThreadPoolExecutor,为任务提供延迟或周期执行,属于线程池的一种。
特性:
使用专门的任务类型—ScheduledFutureTask 来执行周期任务,也可以接收不需要时间调度的任务(这些任务通过 ExecutorService 来执行)
使用专门的存储队列—DelayedWorkQueue 来存储任务,DelayedWorkQueue 是无界延迟队列DelayQueue 的一种。相比ThreadPoolExecutor也简化了执行机制(DelayedWorkQueue内部只允许存储 RunnableScheduledFuture 类型的任务。与 DelayQueue 的不同之处就是它只允许存放 RunnableScheduledFuture 对象,并且自己实现了二叉堆)
支持可选的run-after-shutdown参数,在池被关闭(shutdown)之后支持可选的逻辑来决定是否继续运行周期或延迟任务。并且当任务(重新)提交操作与 shutdown 操作重叠时,复查逻辑也不相同。
七、还可以通过spring bean的方式创建线程池
八、提交线程
用submit()方法,提交一个线程或future对象
九、记得用完后关闭线程池
shutdown() shutdownNow()
执行这两个方法后,isShutdown返回true,全部线程终止后,isTerminated返回true
十、阻塞队列
BlockingQueue为单端队列接口,一端写入,一端读取,当队列为空时,读会阻塞,属于典型的生产者-消费者模式
实现类有:ArrayBlockingQueue、DelayQueue、LinkedBlockingQueue、PriorityBlockingQueue、SynchronousQueue
数组阻塞队列、延迟队列(每个元素会在一个固定时间后才可以被消费)、链式阻塞队列、有优先级的阻塞队列(元素必须实现java.lang.Comparable接口)、同步队列(其实队列只能容纳一个元素,再想写会阻塞)
十一、拒绝策略
1、AbortPolicy
拒绝时抛出异常RejectedExecutionException,是默认的拒绝策略
2、CallerRunsPolicy
只要线程池没有shutdown,立即运行这个线程
3、DiscardPolicy
什么都不做,即直接丢弃
4、DiscardOldestPolicy
丢弃阻塞队列中最老的线程,运行这个线程