简介
线程池无非就是调用方不断的提交任务,线程池有一组线程不断的重任务队列中获取任务,如图所示:
要实现一个线程池,需要考虑几个关键问题:
- 线程池中到任务队列要设置多长,如果是无界,那么很有可能将应用内存耗尽;不是无界队列,那么当队列满了要如何处理?
- 线程池中线程的个数要如何设置,是否要动态变化?
- 每次调用方提交任务时,是直接创建新的线程处理还是放入到队列中等待线程来处理
ThreadPoolExecutor实现原理
ThreadPoolExecutor构造函数
创建线程池使用的是ThreadPoolExecutor这个类,先来看一下这个类的构造函数,这个构造函数的参数是创建线程池的核心参数。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
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:corePoolSize已满,任务队列已满的情况下会扩容线程数到此值
- keepAliveTime/unit:maximumPoolSize中的空闲线程销毁所需要的时间
- workQueue:线程创建工厂
- RejectedExecutionHandler:拒绝策略,corePoolSize、maximumPoolSize、workQueue已满的情况下,任务的拒绝策略
线程池的生命周期
线程池的状态有五种,分别是RUNNING,SHUTDOWN,STOP,TIDYDING和TERMNATED,状态流程如下图:
线程池中有两个关键的函数shutdown(),shutdownNow(),这两个函数会让线程池切换到不同的状态,在ThreadPoolExecutor中,把线程数量和线程池状态用一个字段来表示了,高三位标识存储线程池状态,其余29位存储线程个数
// 初始线程状态为RUNNING,线程数为0
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// 线程池的五种状态
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
任务提交流程
从调用方提交任务到线程池的处理流程如下:
- step1:判断当前线程数是否大于等于corePoolSize,如果小于,则新建线程执行;如果大于则进入step2
- step2:判断任务队列是否已满,如未满,则将提交的任务放入到任务队列;否则进入step3
- step3:判断当前线程数是否大于等于maxPoolSize,如果小于则新建线程处理任务,否则执行step4
- step4:执行拒绝策略
ThreadPoolExecutor中提交任务的代码实现:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
//当前线程数是否大于等于corePoolSize
if (workerCountOf(c) < corePoolSize) {
// 小于则开启新线程
if (addWorker(command, true))
return;
c = ctl.get();
}
// //当前线程数是大于等于corePoolSize,则将任务放入到阻塞队列workQueue中
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//放入队列失败则开新线程
else if (!addWorker(command, false))
//线程数大于maxPooSize,执行拒绝策略
reject(command);
}
addWork开启新线程
// 开启一个新的线程,第二个参数core为true则使用corePoolSize作为上界,如果为fasle,使用maxPoolSize作为上界
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
//只要线程池状态大于或等于SHUTDOWN,说明线程池进入关闭的过程,则不新建一个线程
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
//线程数超过上界corePoooSize或者maxPoolSize不会开新线程
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;
Worker w = null;
try {
// 新建一个work
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
int rs = runStateOf(ctl.get());
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)
addWorkerFailed(w);
}
return workerStarted;
}
任务执行流程
在上面的任务提交过程中,会开启一个新的Worker,并把任务本身作为firstTask赋给该Worker。对于一个Worker来说不仅是要执行任务,还要不断的从队列中获取任务执行。在任务提交流程中会开启线程执行 t.start()方法,这个方法其实最后调用的就是Worker中的run方法:
public void run() {
runWorker(this);
}
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();
// 检测线程池状态,发现线程池已经开始关闭,给自己发送中断信号
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);
}
}
到这里整个线程池的原理就已经分析的差不多了,开头的三个问题在也可以找到答案:
- 线程池中到任务队列要设置多长,如果是无界,那么很有可能将应用内存耗尽;不是无界队列,那么当队列满了要如何处理?
在线程池ThreadPoolExecutor中,任务队列可以由用户定义,决定使用无界还是有界队列,当任务满了之后通过执行不同的拒绝策略来处理后续任务 - 线程池中线程的个数要如何设置,是否要动态变化?
线程池中到线程的个数由corePoolSize和maxPoolSize,是动态变化的 - 每次调用方提交任务时,是直接创建新的线程处理还是放入到队列中等待线程来处理
提交任务是直接创建新的线程还是放入任务队列取决于corePoolSize是否已经满了
上面三个问题解决了,有新的问题ThreadPoolExecutor的构造函数有一个时间参数是用来回收空闲线程的,也就是在maxPoolSize>corePoolSize,任务队列为空时,空闲线程是如何回收的,这一段逻辑在获取getTask中:
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
//如果此时线程池状态大于等于SHUTDOWN返回一个NULL,不需要在领取❤新任务执行
if (rs >= SHUTDOWN, && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// 这一段逻辑很关键,判断是否需要超时等待(wc > corePoolSize)时表示空闲线程需要获取任务时需要超时等待
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
// 如果要超时等待,则等待keepAliveTime的时间
Runnable r = timed
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
可以看到释放空闲线程通过变量来判断是否需要超时等待,在等待keepAliveTime时间后没还没有任务则返回null,跳出任务执行的整个while循环while (task != null || (task = 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中移除
workers.remove(w);
} finally {
mainLock.unlock();
}
}
拒绝策略
线程池的拒绝策略有四种,都实现接口RejectedExecutionHandler,如图所示:
- AbortPolicy:线程池直接抛出异常
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
- CallerRunsPolicy: 在调用方线程里面执行run方法,线程池不处理
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
- DiscardPolicy:直接将任务丢弃,什么都不处理
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
- DiscardOldestPolicy:将队列里面最老的任务丢弃,将该任务放入到队列中
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}