一、线程池是什么?
在多线程编程中,线程池是一种非常重要的技术,它能够有效地管理和复用线程,提高程序的性能和响应速度。特别是在并发访问量较大的情况下,合理地利用线程池可以避免频繁地创建和销毁线程,从而减少系统开销,提高资源利用率。Java语言作为一种广泛应用于企业级开发的编程语言,其线程池技术更是备受关注。本文将深入探讨Java线程池的源码,帮助读者更好地理解线程池技术。
二、源码阅读
1.成员变量和一些基础方法
//初始化为 11100000000000000000000000000000
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3; //29
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
private static final int RUNNING = -1 << COUNT_BITS; // 111
private static final int SHUTDOWN = 0 << COUNT_BITS; // 000
private static final int STOP = 1 << COUNT_BITS; // 001
private static final int TIDYING = 2 << COUNT_BITS; // 010
private static final int TERMINATED = 3 << COUNT_BITS; // 011
在线程池中通过一个变量ctl的高位和低位来表示线程池的状态和线程数量。高3位表示线程池的状态,低29位表示线程池的数量。
// 获取当前线程池的状态
private static int runStateOf(int c) { return c & ~CAPACITY; }
// 获取当前线程数
private static int workerCountOf(int c) { return c & CAPACITY; }
// 创建ctl 由rs和wc 或 操作构成
private static int ctlOf(int rs, int wc) { return rs | wc; }
还有一个关键的内部类Worker
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable{
final Thread thread; //所属线程
/** Initial task to run. Possibly null. */
Runnable firstTask; //执行的任务
//构造函数
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
}
线程池的关键任务就是执行输入进来的任务,线程池可能会复用线程或者创建新的线程执行任务,但是任务都会被封装为Worker类进行执行。
2.构造函数
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.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
参数详解
- corePoolSize:核心线程数,即线程池中保持活动的最小线程数。当有任务提交时,线程池会首先创建这些核心线程来处理任务。
- maximumPoolSize:最大线程数,即线程池中允许的最大线程数量。当任务量超过核心线程数并且工作队列已满时,线程池会创建新的线程,但数量不会超过最大线程数。
- keepAliveTime:线程空闲时间,表示当线程池中的线程数量超过核心线程数时,多余的空闲线程等待新任务的最长时间,超过这个时间会被终止并移除。
- unit:时间单位,用于指定keepAliveTime的时间单位,例如TimeUnit.SECONDS表示秒
- workQueue:工作队列,用于存储等待执行的任务。常用的工作队列包括LinkedBlockingQueue、ArrayBlockingQueue、SynchronousQueue等。
- threadFactory:线程工厂,用于创建新的线程。可以使用默认的Executors.defaultThreadFactory()或自定义实现ThreadFactory接口。
- handler:拒绝策略,表示当工作队列已满且无法继续添加新任务时的处理策略。常见的策略包括AbortPolicy、CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy。
3.一个任务进入线程池的流程
通常我们定义好任务,使用execute方法执行任务
public void execute(Runnable command) {
//如果的传入的任务为空 抛出异常
if (command == null)
throw new NullPointerException();
int c = ctl.get();
// 如果当前线程池的数量小于核心线程数
if (workerCountOf(c) < corePoolSize) {
// 使用addWorker方法 添加线程 然后结束
if (addWorker(command, true))
return;
c = ctl.get();
}
//如果当前线程池的数量大于于核心线程数 并且处于运行状态 将任务加入任务队列中
if (isRunning(c) && workQueue.offer(command)) {
//重新进行检查
int recheck = ctl.get();
// 如果当前线程池不处于运行状态了 移除任务
// 一旦线程池的状态不为running了 就无法接受新的任务
if (! isRunning(recheck) && remove(command))
// 对于任务采取 拒绝策略
reject(command);
// 如果是没有线程 可以创建一个线程
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 如果加入任务队列失败 再创建非核心线程进行 处理 注意 这里addWorker传入的是false
else if (!addWorker(command, false))
// 如果线程已经饱和 那么拒绝任务
reject(command);
}
addWorker()
// 当前任务 和 是否为核心线程
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
//rs >= SHUTDOWN 线程池处于关闭状态 或者已经关闭
// rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty()
// 线程处于关闭状态 且 firstTask为空 且 任务队列中存在任务 那么线程池不应该拒绝新任务 即不返回false
//即线程池处于shutdonw状态时 可以创建无任务线程去 执行工作队列中的任务
//但是不能接受新的任务
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
// 线程数超过最大容量 || 线程池超过核心线程数/最大线程数 返回false
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// 增加线程计数 退出循环
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
// 如果状态发生变化 继续循环
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
// 增加线程计数成功 循环被打破 继续执行
boolean workerStarted = false; // 任务是否开始
boolean workerAdded = false; //任务是否添加到workers中
Worker w = null;
try {
//将任务封装为Worker Worker中会通过线程工厂 创建线程
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());
//如果线程处运行状态 或者 处于shutdown状态态 且firstTask为空
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
// 如果此时线程已经启动要抛出异常 因为还没有运行线程
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start();// 启动线程
workerStarted = true;
}
}
} finally {
if (! workerStarted)//上述过程如果抛出了异常导致添加worker失败
addWorkerFailed(w);
}
return workerStarted;
}
addWorkerFailed
private void addWorkerFailed(Worker w) {
final ReentrantLock mainLock = this.mainLock;
// 加锁
mainLock.lock();
try {
if (w != null)
workers.remove(w);
// 减少线程计数
decrementWorkerCount();
tryTerminate(); //尝试关闭线程池
} finally {
mainLock.unlock();
}
}
一个任务的进入线程池的流程搞清楚了,下面可以看看任务的执行操作。
启动线程后 会调用Worker的run方法
public void run() {
runWorker(this);
}
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 {
// 执行任务 或者 获取任务执行
while (task != null || (task = getTask()) != null) {
w.lock();
//如果线程池处于stoping状态 且线程还没有被中断,就要中断线程
// 如果线程池不处于 stopping状态 那么保证线程不处于中断状态
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;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
线程运行时通过while 循环保证 线程一直可以执行任务,那么如果保证线程停止和非核心线程的销毁呢?
答案就在getTask方法和processWorkerExit 中
getTask()
private Runnable getTask() {
// 获取任务是否超时
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 日常判断 线程池状态 和 任务队列是否为空
// 其实可以看出 如果线程池处于SHUTDOWN状态下 不处理新的任务 但是对于
// 任务队列中的任务还是去执行的。
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null; //返回为空 执行runWoker中的processWorkerExit方法
}
int wc = workerCountOf(c);
// 是否可以淘汰线程
//allowCoreThreadTimeOut 默认为false
//如果allowCoreThreadTimeOut为true 表明所有线程都可以被淘汰
//当线程数量大于核心线程数时表示可以淘汰线程
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
// 如果(线程池数量大于最大线程数 或者 可以淘汰线程并且线程获取任务超时时)
// 而且(线程数大于1 或者 工作队列为空)
// 减少线程计数 并且返回null 让当前线程循环结束
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
// 是否可以超时 一般取决于 线程数是否大于核心线程数
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
// poll 获取任务超时 进入下一次循环
timedOut = true;
} catch (InterruptedException retry) {
// 如果有线程中断了 poll和take都会抛出中断异常 一般会清除中断状态
timedOut = false;
}
}
}
对于线程的关闭,在线程数小于核心线程时,取决于线程池的状态,在线程数大于核心线程时,更容易因为获取任务超时而被关闭。
而且从代码中可以看出 线程处于中断状态下,可能会影响任务的获取,应该只会影响至多一次。因为一般会调用Thread.interrupted()响应中断并且清除线程的中断状态。
对于当前线程的执行,还有最后一个步骤processWorkerExit()
private void processWorkerExit(Worker w, boolean completedAbruptly) {
// 如果线程是突然结束循环的 不改变线程数量
if (completedAbruptly)
decrementWorkerCount();
// 如果线程是因为 获取不到任务 或者 线程池的状态 执行到这个方法中
final ReentrantLock mainLock = this.mainLock;
// 移除当前worker
mainLock.lock();
try {
completedTaskCount += w.completedTasks;
workers.remove(w);
} finally {
mainLock.unlock();
}
tryTerminate();//尝试关闭线程池
int c = ctl.get();
// 如果线程池 处于running 或者 shutdown状态
if (runStateLessThan(c, STOP)) {
// 如果线程不是突然关闭的 且 当前线程数不大于核心线程数 则新增一个线程
if (!completedAbruptly) {
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && ! workQueue.isEmpty())
min = 1;
if (workerCountOf(c) >= min)
return; // replacement not needed
}
addWorker(null, false);
}
}
4.线程池如何关闭
通常关闭线程池使用shutDown方法。
shutDown()
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
// 加锁
mainLock.lock();
try {
checkShutdownAccess();
// 修改线程状态 无限循环 直到修改成功
advanceRunState(SHUTDOWN);
// 中断所有线程
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
// 尝试关闭线程池
tryTerminate();
}
tryTerminate()
final void tryTerminate() {
for (;;) {
int c = ctl.get();
// 如果线程池处于running 或者 已经是关闭状态了
// 或者处于shutdown 状态 并且工作队列不为空
// 直接return
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
// 如果线程数 不为0
if (workerCountOf(c) != 0) { // Eligible to terminate
// 中断一个线程 返回
interruptIdleWorkers(ONLY_ONE);
return;
}
//如果运行到这一步 说明线程池中已经没有线程了
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 修改线程池状态为 tidying
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
//空方法 留给子类进行拓展
terminated();
} finally {
// 修改线程池状态为TERMINATED
ctl.set(ctlOf(TERMINATED, 0));
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
// else retry on failed CAS
}
}
可以看出shutDown方法 是十分宽松的一个方法,修改线程池状态为SHUTDOWN,如果此时任务队列中有任务的话,允许线程池执行完,而且此时不允许加入新的任务到线程池中,在工作队列执行完后调用addWorker方法无法成功,无法再添加新线程了;并且正在执行的线程,执行到工作队列执行空了,后无法获取新任务了,会继续调用processWorkerExit方法,执行tryTerminate修改线程状态并且因为addWorker也无法添加新的线程,到最后所有的线程都会被销毁。
这里举两个例子
假设有两个线程A、B,此时执行了shutDown方法,假设A、B都在等新的任务,此时A被中断了,继续执行getTask方法,发现工作队列为空而且线程池状态为SHUTDOWN,循环中断,执行processWorkerExit,无法添加新的线程,而且调用tryTerminate中断一个线程B,A线程结束循环被销毁,此时B也是如上步骤被销毁。
假设有两个线程A、B,此时执行了shutDown方法,假设A、B都在执行任务队列中的方法,此时A拿到了最后一个方法,执行完毕后继续调用getTask方法,发现工作队列为空而且线程池状态为SHUTDOWN,循环中断,执行processWorkerExit,无法添加新的线程,而且调用tryTerminate中断一个线程B,A线程结束循环被销毁,此时B也是如上步骤被销毁。
综上,调用shutDown方法后,不会立即停止线程池,而是等工作队列中的任务执行完后才会销毁线程。
shutDownNow()
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
// 循环修改线程池状态为STOP
advanceRunState(STOP);
interruptWorkers();
// 获取未执行的任务
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
shutDownNow 方法会让线程池状态修改为STOP并且获取所有工作队列中的任务。即让线程池停止接受新的任务,并且不继续执行完队列中的任务,立即关闭线程池。
如何优雅的关闭线程池?
使用shutDown()和shutDownNow()配合awaitTermination(),进行线程池的优雅关闭。
awaitTermination() 等待线程关闭,可以设置等待时间。
public static void shutdownThreadPoolGracefully(ExecutorService threadPool) {
if (!(threadPool instanceof ExecutorService) || threadPool.isTerminated()) {
return;
}
try {
threadPool.shutdown(); //拒绝接受新任务
} catch (SecurityException e) {
return;
} catch (NullPointerException e) {
return;
}
try {
// 等待 60 s,等待线程池中的任务完成执行
if (!threadPool.awaitTermination(60, TimeUnit.SECONDS)) {
// 调用 shutdownNow 取消正在执行的任务
threadPool.shutdownNow();
// 再次等待 60 s,如果还未结束,可以再次尝试,或则直接放弃
if (!threadPool.awaitTermination(60, TimeUnit.SECONDS)) {
System.err.println("线程池任务未正常执行结束");
}
}
} catch (InterruptedException ie) {
// 捕获异常,重新调用 shutdownNow
threadPool.shutdownNow();
}
//任然没有关闭,循环关闭10次,每次等待10毫秒
if (!threadPool.isTerminated()) {
try {
for (int i = 0; i < 10; i++) {
if (threadPool.awaitTermination(10, TimeUnit.MILLISECONDS)) {
break;
}
threadPool.shutdownNow();
}
} catch (InterruptedException e) {
System.err.println(e.getMessage());
} catch (Throwable e) {
System.err.println(e.getMessage());
}
}
}
线程池会在什么时候加锁
添加和删除worker时会加锁
添加worker失败时会加锁
尝试关闭线程池时会加锁
检查线程状态时会加锁
线程池中断线程时会加锁
调用等待线程池关方法时会加锁