java并发包中的任务调度框架Executor的实现类ThreadPoolExecutor的源码阅读。
ThreadPoolExecutor提供了某些Executor的基本实现,是一个灵活的、稳定的、支持定制的线程池。首先,类结构图如下:
线程池维护着三个容量变量poolSize、corePoolSize、maxPoolSize,以及一个空闲时间keepAliveTime。这四个因素共同决定着线程的创建和销毁。通过调节线程池的基本大小和存活时间,可以帮助线程池回收空闲线程占有的资源。任务调度流程源码:
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) { if (runState == RUNNING && workQueue.offer(command)) { if (runState != RUNNING || poolSize == 0) ensureQueuedTaskHandled(command); } else if (!addIfUnderMaximumPoolSize(command)) reject(command); // is shutdown or saturated } }
分析该方法的注释,可以了解任务的调度流程。当活动线程的数量poolSize小于corePoolSize时,每次收到一个新任务时,都新创建一个线程去执行该任务;如果活动线程规模达到corePoolSize时,新提交的任务开始在工作队列中排队直到队列塞满;当队列塞满时,线程池则动态扩展工作线程数量,直到maxPoolSize;当活动线程数达到maxPoolSize且排队队列已满,仍然有新提交任务时,线程池则执行饱和策略调用RejectedExecutionHandler的rejectExecution方法。
动态扩容活动线程的两个方法addInUnderCorePoolSize和addIfUderMaxinumPoolSize方法操作类似,都是新建工作线程去执行新提交的任务;而ensureQueuedTaskHandler方法则是响应线程池的关闭操作,保证线程池被关闭时,已排队的任务要么被执行要么被抛弃(即不再接受新任务)。源码如下:
private void ensureQueuedTaskHandled(Runnable command) { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); boolean reject = false; Thread t = null; try { int state = runState; if (state != RUNNING && workQueue.remove(command)) reject = true; else if (state < STOP && poolSize < Math.max(corePoolSize, 1) && !workQueue.isEmpty()) t = addThread(null); } finally { mainLock.unlock(); } if (reject) reject(command); else if (t != null) t.start(); }
监测到线程池关闭操作时,如果刚提交的排队任务还没有被执行,则直接抛弃该任务;否则就新建一个工作线程,其初始任务为空,由Worker区轮询排队队列执行已提交的任务。看源码的收获:代码的严谨,finally中执行必要的善后处理;Worker,工作者线程的执行流程,它时Runnable的实现类,run是一个轮询排队队列并执行任务的过程。还有就是对线程池中断操作的响应,保证中断后线程池不再接受新任务。
private Thread addThread(Runnable firstTask) { Worker w = new Worker(firstTask); Thread t = threadFactory.newThread(w); if (t != null) { w.thread = t; workers.add(w); int nt = ++poolSize; if (nt > largestPoolSize) largestPoolSize = nt; } return t; }ThreadPoolExecutor有一个ReentrantLock类型的属性,在execute执行过程中,需要获取这个锁,才能提交任务。也就是说线程池每次只允许有一个线程能够提交任务。这点可以理解。但是,Worker类中也有一个锁,在runTask每次执行任务时都要获取这个锁。为什么工作线程也有一个锁呢?这点还需要仔细想想。仔细分析一下,Worker类的runTask方法需要调用外部类的getTask方法,这个方法会遍历所有的Worker类,调用其interruptIfIdle()方法,代码如下。这就使得Worker类不仅被当前工作线程调用,而且还会被其他工作线程进行空闲检查,这也是runLock的作用,保护工作线程执行任务时不会被其他工作线程干扰。
void interruptIfIdle() { final ReentrantLock runLock = this.runLock; if (runLock.tryLock()) { try { if (thread != Thread.currentThread()) thread.interrupt(); } finally { runLock.unlock(); } } }那么问题来了:工作线程怎么会出现空闲的情况呢?从代码看,工作线程会循环遍历任务队列,如果取不到任务,就一直处于循环状态,也就是所谓的空闲。直到线程池对象被关闭,工作线程才会结束。
public void run() { try { Runnable task = firstTask; firstTask = null; while (task != null || (task = getTask()) != null) { runTask(task); task = null; } } finally { workerDone(this); }