常用线程池分类
- ThreadPoolExecutor
例如: newFixedThreadPool、newSingleThreadExecutor、newCachedThreadPool
2. ScheduledThreadPoolExecutor
可调度线程池例如:newScheduledThreadPool
3. ForkJoinPool 分而治线程池例如newWorkStealingPool
线程池生命周期
Running 能接受提交的任务,并处理阻塞队列中的任务;
shutdown 关闭状态,不接受提交任务,但却可以继续处理阻塞对列中的任务
stop 不接受新任务,不处理队列中任务,会中断正在处理的任务
tidying 如果所有任务都终止了,workerCount为0,线程池进入该状态
terminated 调用terminated方法后进入该状态
//运行
private static final int RUNNING = -1 << COUNT_BITS;
//关闭
private static final int SHUTDOWN = 0 << COUNT_BITS;
//停止
private static final int STOP = 1 << COUNT_BITS;
//
private static final int TIDYING = 2 << COUNT_BITS;
//
private static final int TERMINATED = 3 << COUNT_BITS;
ThreadPoolExecutor 参数
- corePoolSize 线程池核心线程大小
线程池中会维护一个最小的线程数量,即使这些线程处理空闲状态,他们也不会 被销毁,除非设置了allowCoreThreadTimeOut。这里的最小线程数量即是corePoolSize。
- maximumPoolSize 线程池最大线程数量
一个任务被提交到线程池后,首先会缓存到工作队列(后面会介绍)中,如果工作队列满了,则会创建一个新线程,然后从工作队列中的取出一个任务交由新线程来处理,而将刚提交的任务放入工作队列。线程池不会无限制的去创建新线程,它会有一个最大线程数量的限制,这个数量即由maximunPoolSize来指定。
- keepAliveTime 空闲线程存活时间
一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize,那么在指定时间后,这个空闲线程会被销毁,这里的指定时间由keepAliveTime来设定
- unit 空间线程存活时间单位
keepAliveTime的计量单位
- workQueue 工作队列
新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。jdk中提供了四种工作队列:
ArrayBlockingQueue
基于数组的有界阻塞队列,按FIFO排序。新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。当线程池中线程数量达到corePoolSize后,再有新任务进来,则会将任务放入该队列的队尾,等待被调度。如果队列已经是满的,则创建一个新线程,如果线程数量已经达到maxPoolSize,则会执行拒绝策略。
生产和消费锁没有分离LinkedBlockingQuene
基于链表的无界阻塞队列(其实最大容量为Interger.MAX),按照FIFO排序。由于该队列的近似无界性,当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,而不会去创建新线程直到maxPoolSize,因此使用该工作队列时,参数maxPoolSize其实是不起作用的。
锁是分离的 生产用的putLock,消费用的TakeLockAynchronousQuene
一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略。
PriorityBlockingQueue
具有优先级的无界阻塞队列,优先级通过参数Comparator实现。
DelayQuene
延迟队列
六、threadFactory 线程工厂
创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon线程等等
七、handler 拒绝策略
当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,这时如果有新任务提交进来,该如何处理呢。这里的拒绝策略,就是解决这个问题的,jdk中提供了4中拒绝策略:
线程池队列满了以后的处理策略
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
excute实现逻辑
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
/*
*分三步进行:
*
*1。如果运行的线程少于corePoolSize,请尝试
*以给定的命令作为第一个启动新线程
*任务。对addWorker的调用原子性地检查运行状态和
*workerCount,以防止错误警报
*如果不应该的话,则返回false。
*
*2。如果任务可以成功排队,那么我们仍然需要
*再次检查是否应该添加线程
*(因为上次检查后已有的死了)或者
*池自进入此方法后关闭。所以我们
*重新检查状态,必要时回滚排队
*已停止,或启动新线程(如果没有线程)。
*
*3。如果无法对任务进行排队,则尝试添加新的
*线程。如果它失败了,我们知道我们是关闭或饱和
*所以拒绝任务。
*/
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);