为了更全面了解java线程池的运行机制在阅读源码时带着以下几个问题:
1.什么是核心线程数
2.什么是最大线程数
3.什么是任务队列,具体有什么用处
4.线程池最大可执行多少线程
5.任务执行失败如何处理
6.任务提交失败如何处理
7.线程池如何监控
线程池变量:
1.AtomicInteger ctl:原子整型,前3位保存状态信息,后Integer.SIZE - 3为保存线程数;后续Integer.SIZE以32位计算,当前线程池创建后该变量被初始化为运行状态并设置线程数为0
2.BlockingQueue workQueue: 任务队列 3.ReentrantLock mainLock:线程池锁,同步操作线程池状态使用 4.HashSet workers:工作线程 5.int largestPoolSize:线程池曾经到达的最大线程数 6.long completedTaskCount: 线程池完成的任务数统计 等等还有其它变量,可以参考源码线程池常量:
1.COUNT_BITS:保存线程数最大位数,按32位整型计算,最大值为29,也就是线程最大数量为2的29次方减1
2.CAPACITY:容量取0111111111111111111111111111111,一个0,31个1用于计算
3.线程池5种状态常量:
RUNNING: 11100000000000000000000000000000;运行状态
SHUTDOWN: 00000000000000000000000000000000; 关闭状态
STOP: 00100000000000000000000000000000; 停止状态
TIDYING: 01000000000000000000000000000000;清理状态
TERMINATED:01100000000000000000000000000000;终止状态
运行状态:就是当前线程池正在执行;
关闭状态:就是关闭当前线程池,当线程池处于关闭状态时它会执行完任务队列并清空线程池中的线程,线程池状态由SHUTDOWN状态变为TIDYING状态;
停止状态:当线程池处于关闭状态时,中断所有的正在运行的线程并清空任务队列,当线程池线程为空时线程池状态由STOP状态变为TIDYING状态;
清理状态:该状态是终止状态的一个过渡状态,当线程池调用terminated()方法后,线程池状态由TIDYING状态变为TERMINATED状态
ThreadPoolExecutor作为线程池的默认实现看一下类的依赖关系
Executor接口定义了execute方法
/** * Executes the given command at some time in the future. The command * may execute in a new thread, in a pooled thread, or in the calling * thread, at the discretion of the {@code Executor} implementation. * * @param command the runnable task * @throws RejectedExecutionException if this task cannot be * accepted for execution * @throws NullPointerException if command is null */ void execute(Runnable command);
ExecutorService接口在Executor的基础上定义了更加丰富的方法,具体哪些方法查看jdk源码
接下来着重介绍线程池如何提交任务
Executor定义了execute接口用于提交任务到线程池,看看ThreadPoolExecutor如何实现代码解释参考注释
public void execute(Runnable command) { //不允许提交空任务 if (command == null) throw new NullPointerException(); 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(); //double check当前线程池是否是running状态,如果不是则删除当前任务 if (! isRunning(recheck) && remove(command)) reject(command); //检查当前运行的线程数是否为0,如果为0增加一个空任务的worker else if (workerCountOf(recheck) == 0) addWorker(null, false); } //如果任务提交至队列失败,则再次增加worker来运行该任务,如果还是失败则拒绝该任务执行 else if (!addWorker(command, false)) reject(command); }
这里我们有两个问题:
1.在提交任务时为什么要重复检查线程池状态
2.当前无运行线程时为何创建一个空任务的worker,并设置core为false
这两个问题在源码里有明确的注释,而第二个问题为什么提交一个空任务呢?因为提交空任务主要是为了创建一个线程并且让该线程从任务队列中获取任务,根据源码可以知道在创建工作线程时已经把任务提交到任务队列中了
/* * 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.
从该源码我们可以知道当提交一个任务时,线程池先尝试提交到核心线程,然后在尝试提交到任务队列最后才是创建非核心线程。所以假设我们创建了一个线程池核心线程数是10,最大线程数是100,假设缓冲任务队列无限大则该线程池实际上最多在运行的线程数为10。
接下来我们在来看看addWorker的具体实现
private boolean addWorker(Runnable firstTask, boolean core) { retry: for (;;) { int c = ctl.get(); int rs = runStateOf(c); //如果运行状态>=SHUTDOWN,也就是当前状态不为RUNNING状态并且如果当前状态为SHUTDOWN则检查任务队列是否为空或者提交的任务是否为空,如果为false则提交任务失败 //在这段代码带着一个疑问?如果任务队列不为空并且当前状态为SHUTDOWN任务是否可以添加成功?带着这个疑问继续阅读下面代码!!! if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())) return false; for (;;) { int wc = workerCountOf(c); //当当前线程数大于2^29-1时或者如果是core为true则比较当前线程数是否超过核心线程数否则比较最大线程数,如果为true则增加工作线程失败 if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) return false; //原子操作保证记录工作线程数正确,具体去了解Atomic compareAndSet方法 //如果当前工作线程数+1成功则跳出循环 if (compareAndIncrementWorkerCount(c)) break retry; c = ctl.get(); // Re-read ctl //如果当前状态被改变则从retry开始重试,如果不是则在当前循环重试 if (runStateOf(c) != rs) continue retry; // else CAS failed due to workerCount change; retry inner loop } } boolean workerStarted = false; boolean workerAdded = false; Worker w = null; try { //记住如果这个firstTask为null会怎么样这个疑问? w = new Worker(firstTask); final Thread t = w.thread; if (t != null) { final ReentrantLock mainLock = this.mainLock; //这里为什么加可重入锁,如果不加会怎么样?关于可重入锁的实现方式可以去了解AQS的原理 mainLock.lock(); try { // Recheck while holding lock. // Back out on ThreadFactory failure or if // shut down before lock acquired. int rs = runStateOf(ctl.get()); //只有当前线程池状态为RUNNING或者状态为SHUTDOWN并且firstTask为null才允许增加工作线程。这里又有一个疑问为什么为SHUTDOWN了还允许添加工作线程?回到execute方法addWorker(null, false) 这行代码,当线程状态为RUNNING的时候我们恰好把该任务扔进了队列,并且我们又设定了线程池核心线程数为0,在调用addWorker时线程池状态改为了SHUTDOWN,所以此时必须允许创建一个工作线程去消费任务队列中的那个任务 if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) { if (t.isAlive()) // precheck that t is startable throw new IllegalThreadStateException(); //workers是hashset线程不安全,所以我们在执行这段代码时加了锁回答了上面那个问题 workers.add(w); int s = workers.size(); if (s > largestPoolSize) largestPoolSize = s; workerAdded = true; } } finally { mainLock.unlock(); } if (workerAdded) { //当工作线程添加成功时启动线程 //此时我们还有一个问题未解决,那就是firstTask为null这个线程会怎么执行,等下我们去看work的run方法来找到答案 t.start(); workerStarted = true; } } } finally { if (! workerStarted) //回滚添加失败的工作线程 addWorkerFailed(w); } return workerStarted; }
接下来我们继续看Worker的run方法调用的runWorker
final void runWorker(Worker w) { Thread wt = Thread.currentThread(); Runnable task = w.firstTask; w.firstTask = null; w.unlock(); // allow interrupts boolean completedAbruptly = true; try { //如果创建工作线程firstTask为null则会从任务队列中获取,如果队列中返回的任务为空则释放当前工作线程 while (task != null || (task = getTask()) != null) { w.lock(); // If pool is stopping, ensure thread is interrupted; // if not, ensure thread is not interrupted. This // requires a recheck in second case to deal with // shutdownNow race while clearing interrupt //如果当前线程池状态为STOP并且当前工作线程未中断则中断当前线程,注意:仅仅只是把线程标识为中断并不影响当前线程执行。所以就算当前状态为STOP已经从队列里面获取的任务还是会被执行 //所以这里又有一个问题,STOP状态跟SHUTDOWN状态又有什么区别? //区别可以查看shutdown()和shutdownNow()的源码,STOP状态会把任务队列全部清空,已经被工作线程获取的任务等待执行完毕后回收线程转为TIDYING状态;SHUTDOWN状态则不会清空任务队列,等待工作线程把已经提交到任务队列的任务全部执行完毕在回收线程然后转为TIDYING状态 //如果不是STOP以上的状态则清除中断标志 if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && //double check如果清除中断标志后当前状态恰好又变为STOP,则wt.isInterrupted()必为false则调用wt.interrupt() runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted()) wt.interrupt(); try { //终于看到一个很有用的调用方法了!!! beforeExecute(wt, task); Throwable thrown = null; try { task.run(); } catch (RuntimeException x) { thrown = x; throw x; } catch (Error x) { thrown = x; throw x; } catch (Throwable x) { thrown = x; throw new Error(x); } finally { //又看到一个很有用的调用方法!!! //beforeExecute和afterExecute方法在扩展线程池功能时非常有用,官方文档里面给了一个扩展案例PausableThreadPoolExecutor,可以参考ThreadPoolExecutor的类javadoc afterExecute(task, thrown); } } finally { task = null; w.completedTasks++; w.unlock(); } } //false非异常情况退出,true异常退出 completedAbruptly = false; } finally { //退出当前工作线程 //这里有个疑问不是在不设allowCoreThreadTimeOut时核心线程如何保持不被释放?keepAliveTime有什么用?这个时候我们就要去看任务队列的用处了 processWorkerExit(w, completedAbruptly); } }
在上面源码我们引入了任务队列,线程池的所有任务队列必须实现BlockingQueue,这里不详细介绍BlockingQueue的所有实现细节,我们着重介绍下以下几个线程池用到的方法:
boolean offer(E e) 我们在execute源码里见过了, E poll(long timeout, TimeUnit unit)和E take()上述源码还没见过,那接下来我们继续看runWorker调用的getTask()方法,它是如何从任务队列中获取一个执行任务的
private Runnable getTask() { boolean timedOut = false; // Did the last poll() time out? for (;;) { //获取当前状态 int c = ctl.get(); //获取工作线程数量 int rs = runStateOf(c); // Check if queue empty only if necessary. //如果当前状态大于SHUTDOWN并且当前状态大于STOP或者任务队列为空 //也就是有如下两种情况,如果当前状态为SHUTDOWN并且队列为空或者当前当前不为RUNNING和SHUTDOWN状态则返回空任务是否当前线程 if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { decrementWorkerCount(); return null; } int wc = workerCountOf(c); // Are workers subject to culling? //我们是否允许核心线程超时释放 //如果允许核心线程超时释放则当等待线程超过keepAliveTime的时间后释放线程 //如果不允许 boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) { if (compareAndDecrementWorkerCount(c)) return null; continue; } try { //如果允许释放过期的空闲工作线程 //则调用poll在指定keepAliveTime之后如果当前任务队列没有任务则返回null //如果不允许释放过期的空闲线程则调用take()无限期等待知道任务队列有任务 //关于poll和take可以参考BlockingQueue的实现类源码 Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); if (r != null) return r; timedOut = true; } catch (InterruptedException retry) { timedOut = false; } }}
通过上述源码可以知道当getTask()方法返回null后将会释放工作线程,接下来继续看processWorkerExit做了哪些事情
private void processWorkerExit(Worker w, boolean completedAbruptly) { //如果是异常中断,则当前工作线程数量减一 if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted decrementWorkerCount(); final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { completedTaskCount += w.completedTasks; workers.remove(w); } finally { mainLock.unlock(); } tryTerminate(); int c = ctl.get(); if (runStateLessThan(c, STOP)) { if (!completedAbruptly) { //如果核心线程允许释放,怎最小工作线程数为0,否则为核心线程数 int min = allowCoreThreadTimeOut ? 0 : corePoolSize; //如果最小线程为0,并且当前任务队列不为空,为防止任务队列无法被消费则把最少线程数设为1 if (min == 0 && ! workQueue.isEmpty()) min = 1; //当工作线程比最少线程数要大,则直接返回,否则调用addWorker创建一个新的工作线程 if (workerCountOf(c) >= min) return; // replacement not needed } addWorker(null, false); } }
通过上面的源码分析开始我们提到的几个问题可以做出解答了
1.什么是核心线程数?核心线程数开始时会随着任务的增加逐渐增加到核心线程数的最大值,当当前任务比核心线程数更多时,任务将会被放进任务队列
2.什么是最大线程数?当任务队列被任务放满后,线程池会扩大工作线程,直到最大线程数,如果不设置允许核心线程过期,则在任务消费完之后,比核心线程数多余的空闲线程将会等待keepAliveTime的时间后释放
3.什么是任务队列,具体有什么用处?任务队列的作用是缓冲过多的提交任务,防止线程池创建过多的线程。
4.线程池最大可执行多少线程?线程最大数量为2的Integer.SIZE-3次方减1
5.任务执行失败如何处理?如果要处理执行失败的用户,我们可以扩展线程池功能继承并重写afterExecute方法
6.提交任务失败如何处理?线程池在任务提交失败后会调用RejectedExecutionHandler接口的reject(command)方法。我们可以实现RejectedExecutionHandler接口或者使用线程池自带的失败处理策略:AbortPolicy、DiscardOldestPolicy等等
7.线程池如何监控?ThreadPoolExecutor提供了一些接口供用户监控线程池的使用情况,例如getPoolSize()获取当前工作线程数量,getActiveCount()当前的活动线程数量(该方法大家可以去看下源码,很多人的实现可能会有在Worker加一个变量isActive来标识当前线程是空闲的还是活动的)等等