线程池原理分析
线程池好处
-
创建/销毁线程需要消耗系统资源,线程池可以复用已创建的线程
-
控制并发的数量,并发数量过多,可能会导致资源消耗过多,从而造成服务器崩溃。(主要原因)
-
可以对线程做统一管理
线程池原理
线程池结构
从图中可以看出,ThreadPoolExecutor
继承了 AbstractExecutorService
类, AbstractExecutorService
实现了 ExecutorService 接口,ExecutorService
接口继承了 Executor
接口。具体每个接口和类的方法可以查看下图
-
Executor
接口
ExecutorService
接口
-
AbstractExecutorService
类
上面两个接口,定义了很多的接口方法,包括 execute执行的核心方法,还有一些设置线程状态等方法
// 关闭线程池,已提交的任务继续执行,不接受继续提交新任务
void shutdown();
// 关闭线程池,尝试停止正在执行的所有任务,不接受继续提交新任务
// 它和前面的方法相比,加了一个单词“now”,区别在于它会去停止当前正在进行的任务
List<Runnable> shutdownNow();
// 线程池是否已关闭
boolean isShutdown();
// 如果调用了 shutdown() 或 shutdownNow() 方法后,所有任务结束了,那么返回true
// 这个方法必须在调用shutdown或shutdownNow方法之后调用才会返回true
boolean isTerminated();
// 等待所有任务完成,并设置超时时间
// 我们这么理解,实际应用中是,先调用 shutdown 或 shutdownNow,
// 然后再调这个方法等待所有的线程真正地完成,返回值意味着有没有超时
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;
// 提交一个 Callable 任务
<T> Future<T> submit(Callable<T> task);
// 提交一个 Runnable 任务,第二个参数将会放到 Future 中,作为返回值,
// 因为 Runnable 的 run 方法本身并不返回任何东西
<T> Future<T> submit(Runnable task, T result);
// 提交一个 Runnable 任务
Future<?> submit(Runnable task);
// 执行所有任务,返回 Future 类型的一个 list
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException;
// 也是执行所有任务,但是这里设置了超时时间
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException;
// 只有其中的一个任务结束了,就可以返回,返回执行完的那个任务的结果
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException;
//同上一个方法,只有其中的一个任务结束了,就可以返回,返回执行完的那个任务的结果,
// 不过这个带超时,超过指定的时间,抛出 TimeoutException 异常
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
FutureTask类是Runable的实现类,FutureTask<V> implements RunnableFuture<V>,interface RunnableFuture<V> extends Runnable, Future<V>,然后每个Runnable通常包装成FutureTask,然后调用executor.execute(Runnable command)将其提交给线程池。Runnable 的 void run() 方法是没有返回值的,所以,通常,如果我们需要的话,会在 submit 中指定第二个参数作为返回值。
-
ThreadPoolExecutor
核心方法
-
构造方法
execute(Runnable runnable)方法,是交给具体的执行器去实现,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
:最大线程数,线程池允许创建的最大线程数。 -
workQueue
:任务队列,BlockingQueue接口的某个实现类。 -
keepAliveTime
:非核心线程闲置的活跃时间,非核心线程的如果处于闲置状态的时间超过该值,则会被销毁。如果设置了 allowCoreThreadTimeOut(true),则会作用与核心线程。 -
unit
:keepAliveTime 的时间单位。 -
threadFactory
:用于生成线程的工厂。 -
handler:当线程池已经满了,但是又有新的任务提交的时候,该采取什么策略由这个来指定。有几种方式可供选择,像抛出异常、直接拒绝然后返回等,也可以自己实现相应的接口实现自己的逻辑。
-
ThreadPoolExecutor.AbortPolicy
: 默认的拒绝策略,丢弃任务并抛出RejectedExecutionException
异常。public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString()); }
-
ThreadPoolExecutor.DiscardOldestPolicy
: 如果线程池没有关闭,则丢弃最久没被处理的任务。然后执行当前任务。public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { e.getQueue().poll(); e.execute(r); } }
-
ThreadPoolExecutor.DiscardPolicy
:丢弃当前任务,不做任何操作。public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { }
-
ThreadPoolExecutor.CallerRunsPolicy
:如果线程池没有关闭,则由调用线程处理该任务。public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { r.run(); } }
-
-
execute(Runnable command)
-
ctl.get(): 32位,“线程池状态” 和 “线程数” 的整数,状态是前三位,线程数是后29位
-
workerCount:正在工作的线程数量
-
runState:线程池运行状态
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); /* * Proceed in 3 steps: * 1. 当前正在运行的线程数量少于核心数,尝试调用 addWorker 开启一个新线程执行任务。 * 调用 addWorker 会再次检查 runState、workCount 判断新建线程的情况,返回false * 表示新建线程失败, 返回 true,流程结束。 * * 2. 如果任务能够成功入队,再次 double-check线程状态、线程数量,判断是否应该添加线程。 * 因为自从上次check后,可能存在线程已经执行完毕,或者线程池已经shut down,如果存在上 * 述的问题,那么就会回滚执行出队操作。 * * 3. 如果不能入队成功,则会调用addWorker开启一个线程执行。如果开启失败,则执行拒绝策略。 */ int c = ctl.get(); // 当前线程数少于核心线程数,那么直接调用addWorker添加一个worker执行任务。 // true 为核心线程,false为非核心线程。线程数在 [0, corePoolSize)直接开启新的线程 if (workerCountOf(c) < corePoolSize) { // 执行成功后则直接return if (addWorker(command, true)) return; c = ctl.get(); } // 两种情况会走到这里: // 1. 当前线程数 >= 核心线程数。 // 2. 或者上一步调用addWorker新建核心线程失败。 if (isRunning(c) && workQueue.offer(command)) { // 线程池状态为正在运行并且将任务加入阻塞队列成功 // 再次检查线程池状态、数量, int recheck = ctl.get(); // 如果线程池没有运行,则移除刚刚入队的任务。如果移除成功,则执行拒绝策略 if (! isRunning(recheck) && remove(command)) reject(command); // 走到这里前提:线程正在运行或者移除任务失败 // 因为出队失败,那么刚刚添加的任务还在阻塞队列中,如果线程数为0, //新建非核心线程执行阻塞队列中的任务。意图是:担心任务提交到队列中了,但是线程都关闭了 else if (workerCountOf(recheck) == 0) addWorker(null, false); } // 走到这里:线程池没有运行或者任务入队失败(队列满了) // 直接调用addWorker新建非核心线程执行任务 else if (!addWorker(command, false)) // 线程数 >= maximumPoolSize,addWorker失败,执行拒绝策略 reject(command); }
为什么二次检查double-check?
因为如果任务已经加入阻塞队列,但是万一线程池没有处于运行状态,那么刚入队的任务永远不会执行。
总结流程:
-
线程数量 < corePoolSize,无论线程是否空闲,都会新建一个核心线程执行任务。
-
线程数量 >= corePoolSize ,任务会进入工作队列中,然后空闲的核心线程会依次去缓存队列中取任务执行(体现了线程复用)。
-
当工作队列满了,新建非核心线程执行当前任务。
-
工作队列满了、线程数 >= maximumPoolSize,执行拒绝策略。
-
-
addWorker(Runnable firstTask, boolean core)
:真正创建工作线程的方法-
firstTask
:当前任务。可以为null,表示执行工作队列中的任务 -
core
:是否新建核心线程,true:使用核心线程数corePoolSize
作为创建线程的界限,false:使用最大线程数maximumPoolSize
作为界限
private boolean addWorker(Runnable firstTask, boolean core) { retry: for (;;) { int c = ctl.get(); // 获取线程运行状态 int rs = runStateOf(c); // 线程池状态大于 SHUTDOWN,其实也就是 STOP, TIDYING 或 TERMINATED // 当状态大于 SHUTDOWN 时,不允许提交任务,且中断正在执行的任务 // 满足 rs > SHUTDOWN、firstTask 不会空,工作队列不为空时,直接返回false if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())) return false; // for (;;) { // 获得工作线程数量 int wc = workerCountOf(c); // core为true时,wc >= corePoolSize // 或者core为false时,wc >= maximumPoolSize 直接返回false if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) return false; // CAS 增加工作线程数 + 1,成功则中断for循环 if (compareAndIncrementWorkerCount(c)) break retry; c = ctl.get(); // Re-read ctl // CAS 失败,判断重新读取的线程状态,如果与初始的rs状态不一致, // 那么回到for里层再次执行 if (runStateOf(c) != rs) continue retry; // else CAS failed due to workerCount change; retry inner loop } } // 走到这里说明线程池的状态是正确的或者工作线程已经自增1 // worker 是否已经启动 boolean workerStarted = false; // 是否已将这个 worker 添加到 workers 这个 HashSet 中 boolean workerAdded = false; Worker w = null; try { // 创建一个线程工作者 w = new Worker(firstTask); final Thread t = w.thread; // 创建成功 if (t != null) { final ReentrantLock mainLock = this.mainLock; // 获取全局锁 mainLock.lock(); try { // Recheck while holding lock. // Back out on ThreadFactory failure or if // shut down before lock acquired. int rs = runStateOf(ctl.get()); // 小于 SHUTTDOWN 那就是 RUNNING,这个自不必说,是最正常的情况 // 如果等于 SHUTDOWN,前面说了,不接受新的任务,但是会继续执行等待队列中的任务 if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) { // 刚刚新建的线程状态还未启动,alive表示线程启动但是还未死亡 if (t.isAlive()) // precheck that t is startable throw new IllegalThreadStateException(); // 将当前worker添加进线程中的workers,存放的都是工作线程 workers.add(w); int s = workers.size(); // 记录线程池曾经达到的最大线程数量值 if (s > largestPoolSize) largestPoolSize = s; // 设置worker状态为已添加 workerAdded = true; } } finally { mainLock.unlock(); } // 添加成功则启动线程 if (workerAdded) { // 这个方法很重要,直接工作方法,这里会触发Worker类的run方法被JVM调用, // run方法中又会调用runWorker方法 t.start(); workerStarted = true; } } } finally { // 刚才说了进入到这个try代码块时,已经将workercount工作数量加1 // 这里时如果工作线程没有开启,那么会回滚。将wc - 1 if (! workerStarted) addWorkerFailed(w); } return workerStarted; }
-
-
Woker.runWorker(Worker w)
: 工作线程执行任务的具体方法-
w:需要执行的工作者线程
final void runWorker(Worker w) { Thread wt = Thread.currentThread(); // 获取当前工作线程需要执行的任务 Runnable task = w.firstTask; w.firstTask = null; // 释放锁 w.unlock(); // allow interrupts boolean completedAbruptly = true; try { // 如果task为空的话,则会循环去调用 getTask方法获取任务 // getTask则是从工作队列中获取的头部任务 while (task != null || (task = getTask()) != null) { w.lock(); // 如果线程池状态大于等于 STOP,那么意味着该线程也需要中断 if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && 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 { // 钩子方法,可以自己自定义实现 afterExecute(task, thrown); } } finally { task = null; // 完成任务数自增1 w.completedTasks++; w.unlock(); } } completedAbruptly = false; } finally { // 如果到这里,需要执行线程关闭: // 1. 说明 getTask 返回 null,也就是说,队列中已经没有任务需要执行了,执行关闭 // 2. 任务执行过程中发生了异常 // 第一种情况,已经在代码处理了将 workCount 减 1,这个在 getTask 方法分析中会说 // 第二种情况,workCount 没有进行处理,所以需要在 processWorkerExit 中处理 processWorkerExit(w, completedAbruptly); } }
线程池怎么复用线程?
首先执行创建这个 worker 时就有需要执行的当前任务,当执行完当前任务后,worker 的生命周期没有结束,而是在 while 循环中,前提是线程池和当前线程的状态正常,那么worker会不断地调用
getTask
方法从工作队列中获取任务然后调用task.run()
执行任务,从而达到线程复用的目的。只有getTask
方法不返回 null,此线程则不会退出关闭。 -
-
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. // 检查rs状态如果线程池状态为不可用,工作队列是空的, // 将工作线程数 - 1,最后直接返回null if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { decrementWorkerCount(); return null; } int wc = workerCountOf(c); // Are workers subject to culling? // 设置核心线程空闲时是否会被销毁,默认为false,如果为true则会被销毁 boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; // 1. 如果wc>最大线程数并且工作队列时空的,递减wc // 2. 如果设置 allowCoreThreadTimeOut为true 或者wc > corePoolSize && wc <= maximumPoolSize // 并且在规定时间没有获取到任务。则会递减wc if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) { if (compareAndDecrementWorkerCount(c)) return null; continue; } try { // timed为true,则设置超时时间获取任务,否则使用take,一直阻塞,直到 //队列中有任务加入时,线程才会被唤醒。这里就是保证核心线程不被回收的关键 Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); if (r != null) return r; timedOut = true; } catch (InterruptedException retry) { timedOut = false; } } }
核心线程会一直卡在
workQueue.take
方法,被阻塞挂起,不会占用CPU资源,直到获取到Runnable任务然后返回。如果allowCoreThreadTimeOut
设置为true,那么所有的工作线程都会去使用 poll 调用,当返回null时,线程会被销毁。非核心线程调用 poll 超时未获取到时,
timedOut = true
, 下一次while循环判断时,timed = wc >corePoolSize = true
,然后调用compareAndDecrementWorkerCount
后,直接返回 null。Worker对象的run()
方法循环体的判断为 null,任务结束,线程被回收。