Executors创建线程池原理
Executors创建线程池原理
1、ExecutorService接口
- 通过构造参数创建
- 通过Executors创建
实现类ThreadPoolExecutor构造参数为:
//全参构造函数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
- corePoolSize:核心线程数
- maximumPoolSize:最大线程数
- keepAliveTime:超过核心线程数的线程空闲时间
- unit:空闲时间的单位
- workQueue:工作队列
- threadFactory:创建线程的工厂
- handler:任务超出队列后的处理策略
2、线程池的工作流程
(1)执行新的任务,如果线程数还没有超过核心线程数,就创建线程执行任务
(2)如果线程数已经超过核心线程数,就将任务放入工作队列
(3)如果线程数已经超过核心线程数且工作队列已满,查看线程数是否超过最大线程数,如果没有,就创建新的线程,将工作队列中的任务放入线程中执行。
(4)如果达到最大线程数,就将新的任务交给处理策略进行处理
(5)当核心线程数可以处理所有的任务时,超过核心线程数的线程空闲时间超过keepAliveTime后,就销毁线程。
3、Executors常用的线程池
(1)newFixedThreadPool
单参数,指的是线程数,指定核心线程数和最大线程数,都通过该参数指定
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
- 核心线程数和最大线程数使用同样的数量
- 超过核心线程数的线程空闲时间设置为0(不会超过)
- 工作队列使用的是LinkedBlockingQueue,无界阻塞队列,该队列可以无限制的放入任务
(2)newSingleThreadExecutor
默认无参,也可传入ThreadFactory自定义创建线程的工厂
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
- 创建核心线程数是一个线程的线程池
- 超时时间为0
- 无界阻塞队列
(3)newCachedThreadPool
默认无参,也可传入ThreadFactory自定义创建线程的工厂
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
- 创建核心线程数为0,最大线程数为Integer最大值
- 当池中的线程空闲时间超过60秒,销毁线程
- SynchronousQueue工作队列,属于同步移交队列,不是一个真正的队列,要放入这个队列,必须要有线程等待接收任务,才会放入队列,并移交给线程
(4)newScheduledThreadPool
单个参数传入核心线程数
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
该线程池,支持定时以及周期性的执行任务。
ScheduledThreadPoolExecutor构造参数:
传入父类的参数中:核心线程数为参数,最大线程数为Integer最大值,超过核心线程数的线程空闲时间0,阻塞队列使用DelayedWorkQueue延迟工作队列。
其父类中使用如下参数,除去传递的参数,Factory使用默认的线程工厂,饱和策略使用默认的策略defaultHandler=AbortPolicy,当任务超出队列后抛出异常。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
4、饱和策略
当核心线程数已满,任务队列已满且达到了最大线程数时。
(1)AbortPolicy:抛出异常,让使用者自己捕获异常进行处理
(2)DiscardPolicy:什么也不做,不抛异常
(3)DiscardOldestPolicy:丢弃队列中队首的任务,处理新的任务,这种情况下,如果队列使用的时优先级队列,会抛出优先级最高的任务,不建议将该策略与优先级队列一同使用。
(4)CallerRunsPolicy: 既不抛弃任务也不抛出异常,直接运行任务的run方法,将任务回退给调用者来直接运行。使用该策略时线程池饱和后将由调用线程池的主线程自己来执行任务,因此在执行任务的这段时间里主线程无法再提交新任务,从而使线程池中工作线程有时间将正在处理的任务处理完成。
5、任务队列类型
-
无界阻塞队列:没有大小限制的队列,常用的无界队列为 LinkedBlockingQueue ,当某个任务执行比较耗时时,新的任务都会堆积在该队列中
-
有界阻塞队列:
常用的有两类,一类是遵循FIFO原则的队列如ArrayBlockingQueue与有界的LinkedBlockingQueue,另一类是优先级队列如PriorityBlockingQueue。PriorityBlockingQueue中的优先级由任务的Comparator决定。
使用有界队列时队列大小需和线程池大小互相配合,线程池较小有界队列较大时可减少内存消耗,降低cpu使用率和上下文切换,但是可能会限制系统吞吐量。 -
同步移交队列: 如果不希望任务在队列中等待而是希望将任务直接移交给工作线程,可使用SynchronousQueue作为等待队列。SynchronousQueue不是一个真正的队列,而是一种线程之间移交的机制。要将一个元素放入SynchronousQueue中,必须有另一个线程正在等待接收这个元素。只有在使用无界线程池或者有饱和策略时才建议使用该队列。