自定义线程池的核心:ThreadPoolExecutor
为了更好的控制多线程,JDK提供了一套线程框架Executor,帮助开发人员有效的进行线程控制,其中在Java.util.concurrent包下,是JDK并发包的核心,比如我们熟知的Executors。Executors扮演着线程工厂的角色,我们通过它可以创建特定功能的线程池,而这些线程池背后的就是:ThreadPoolExecutor。那么下面我们来具体分析下它。
构造ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
corePoolSize:核心线程数
maximumPoolSize:最大线程数
keepAliveTime + unit:线程回收时间
workQueue:任务较多时暂存到队列
threadFactory:执行程序创建新线程时使用的工厂
handler:超出线程池容量以及队列长度后拒绝任务的策略
有界队列 or 无界队列
我们知道对于BlockingQueue而言,有典型的有界队列ArrayBlockingQueue以及LinkedBlockingQueue这种无界队列,那么对于线程池而言,workQueue采用有界队列、无界队列会产生什么影响呢?如果采用无界队列,那么会存在拒绝策略吗?显然不会,因为容量是无限的,比如没有预定义容量的LinkedBlockingQueue。比如,在某个时段突发很多请求,那么采用无界队列就保证了增长的可能性,而不是拒绝。如果采用有界队列,相比无界队列而言,有助于防止资源耗尽,在实际中我们经常在拒绝策略中记录log,然后通过定时任务的方式进行处理。
接着上面的分析思路,想一想采用有界队列、无界队列中线程池的大小,这里涉及到一个问题,那就是任务增长时,是先增长至maximumPoolSize,还是先暂存到队列中的问题?
corePoolSize and maximumPoolSize
一个是核心线程数,一个是最大线程数,怎么理解呢?在线程池初始化阶段,是否已经初始化好corePoolSize个线程呢?大量任务来了后,线程池中的线程怎么变化?任务完成处理后,线程池又是如何回收的,回收到什么程度?
下面先来看一个线程池处理流程图:
简单一句话:提交任务,线程池中的线程数可以增长至corePoolSize,之后继续提交任务将暂存至队列中,如果队列满,则看是否能继续增长线程数至maximumPoolSize,超出后将进行拒绝策略处理。显然,如果采用无界队列,那么maximumPoolSize将失效,线程池中的线程最多就是corePoolSize个线程工作。
从这里我们也可以看出,在需要判断是否corePoolSize是否已经达到,需要锁的介入,因此我们应尽量让线程启动后线程池的大小就处于>=corePoolSize个数,提前预热。
keepAliveTime + unit
这里涉及到线程池回收线程。简单来说,就是采用有界队列,导致corePoolSize满,队列满,不得已线程池的线程增长至maximumPoolSize,那么任务处理完毕后,线程池中多出corePoolSize的部分理应回收,那么等待多长时间,多长时间没有任务后在进行回收的问题就是由上面的参数决定。
Executors创建线程池实例分析
固定线程池
特点:
无界队列,导致keepAliveTime/unit/maximumPoolSize失效,不存在拒绝;
随着任务的增长,线程数将固定在corePoolSize。
缓存线程池
特点: