打算把JUC下常用的几个类源码都看一遍并做记录,今天是线程池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.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;
}
接着我们从常用的执行方法execute()
来入手分析源码。我会通过带序号的注释对源码进行解析,按顺序看注释即可。
进入源码阅读前先进行一个补充说明
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* 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.
*/
int c = ctl.get();
// 1、判断当前线程数是否小于核心线程数
if (workerCountOf(c) < corePoolSize) {
// 2、小于则添加Worker并运行提交的任务,true表示添加的是核心线程Worker
if (addWorker(command, true))
return;
c = ctl.get();
}
// 3、如果核心线程数已满,则判断当前是否还是可运行状态
// 如果是,则把任务提交到阻塞队列尾部
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
// 4、提交成功后,需要判断会不会线程池不可运行了,如果不可运行则把提交的任务去掉
// 调用拒绝策略
if (! isRunning(recheck) && remove(command))
reject(command);
// 5、如果线程池还能运行,但是线程数却没有了(发生意外全被中断了)
// 则新建一个线程保证刚刚提交的任务能够执行完成
// 注意这里的worker新增时不需要携带任务去执行,因此新增后会直接去队列中获取任务
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 6、如果队列已满,则添加非核心线程来执行溢出队列的任务
else if (!addWorker(command, false))
// 7、如果非核心线程也满了,则拒绝
reject(command);
}
阅读以上代码自问自答下述问题
那么excute()
只能让我们明白线程是如何被创建的,至于线程如何执行任务,如何从队列中获取任务,以及如何添加worker都还不知道,所以我们接着看
该方法是添加线程时调用的,我在点开其实现前带着一个问题
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
// 1、判断当前线程池状态是否为可运行
if (rs >= SHUTDOWN && //非可运行
! (rs == SHUTDOWN && //非SHUTDOWN
firstTask == null && // 任务不为空
! workQueue.isEmpty())) // 队列不为空
return false;
// 目的为了占锁
for (;;) {
int wc = workerCountOf(c);
// 2、如果线程池为可运行,则根据core判断需要添加的是核心还是非核心
// 分别判断是否超出
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// 3、通过CAS让线程数加一来保证线程安全,如果CAS成功则退出循环
// 否则继续尝试
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
// 4、如果发现运行状态发生了改变则重新进入循环
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 {
// 5、把任务交给一个新的worker,worker会new一个新的线程
w = new Worker(firstTask);
// 6、获取到线程,如果待会worker成功加入线程池,则可以执行该线程
final Thread t = w.thread;
if (t != null) {
// 7、由于线程池要考虑线程安全,这里使用可重入锁来加锁
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());
// 8、加锁成功后,再检查一遍线程池的状态
// rs
if (rs < SHUTDOWN ||
// 9、表示在抢到锁后,线程池刚好变成SHUTDOWN还是可以执行的
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
// 10、虽然workers是一个非线程安全的HashSet
// 但由于已经加了锁所以可以直接add
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
// 11、如果worker成功加入线程池,那么可以start对应线程了
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
可以回答一开始的问题了
private final HashSet workers = new HashSet();
try/finally
保证锁的释放这些就不赘述Worker在start
之后会调用其run
方法,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 {
// 1、判断task是否不为空,不为空则直接执行
// 为空则试图从阻塞队列中获取task,如果阻塞队列为空则会等待
// 如果设置了超时时间那么会在超时后返回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
// 2、判断线程池状态,如果要求停止则在执行前把当前线程中断状态置为真
// 意味着当发生阻塞时会被中断
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
// 3、可以重写该方法自定义实现一些运行前的逻辑
beforeExecute(wt, task);
Throwable thrown = null;
try {
// 4、执行任务
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);
}
}
unlock
,以及为什么执行方法时要lock
自己那么我们先放着上述问题,去看看线程池怎么SHUTDOWN以及STOP的
我们先看看切换SHUTDOWN和STOP状态的源码
先看SHUTDOWN的源码
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(SHUTDOWN);
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
interruptWorkers()
那么我们接着看interruptWorkers()
我们直接进入interruptIdleWorkers()
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
runWorker
时,在task.run()
前必须得获得锁,也就意味着,如果worker的线程正在执行任务,那么当前中断线程是获取不到锁的,tryLock不会阻塞,而是直接失败注意了,t.interrupt()
只是改变了中断状态的值,我们还要去思考改了值后,线程会在哪里被中断,是否能实现SHUTDOWN的功能?
runWorker
中一开始就调用w.unlock
释放锁了
w.lock
,B线程进行w.unlock
会报IllegalMonitorStateException
的异常(如下小demo)runWorker
方法,main线程就调用shutdown
抢先一步加锁并且设置中断,则该worker执行到第一步w.unlock
时就会被中断了,因为上锁的是main线程,worker线程无法解锁 ReentrantLock lock = new ReentrantLock();
new Thread(() -> {
lock.lock();
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "input thread name").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
lock.unlock();
}, "input thread name").start();
Exception in thread "input thread name" java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:457)
at hx.learn.Check.lambda$main$1(Check.java:51)
at java.lang.Thread.run(Thread.java:748)
那么我们再思考一下线程被修改了中断状态后会在哪里被中断,会带来什么影响呢?
至此,SHUTDOWN我们就解决了哈,就剩下STOP了
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(STOP);
interruptWorkers();
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
tasks = drainQueue();
的操作,就是把队列中的task拷贝出来,并把队列清空,就不具体展开了,因此我们是能够获取队列未执行的任务的那么我们思考一下所有worker的中断状态都为true后,能否实现STOP关于中断运行时线程的功能?
关于线程池的超时部分,异曲同工,就不继续分析了,以及submit方法和阻塞队列的实现也先不分析了,写真么多也有点累了
那么以上就是我一边看源码一边思考的记录了,感觉已经能够比较好地掌握线程池,同理,对于连接池等实现应该就更简单了,最后总结一些不是很常见的点