概述
这篇文章主要从代码层面分析线程池的工作流程,如果,想直接知道线程池的工作流程,可以看上一篇文章Java线程池
享受源代码的快乐之旅开始了
首先是,总体执行流程
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();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//尝试着将任务加入非核心线程,若加入失败,拒绝执行
else if (!addWorker(command, false))
reject(command);
}
总结一下
- 核心线程数未满,直接创建新的核心线程来执行任务,若有空闲线程,复用空闲线程
- 核心线程数已满,要执行的任务加入堵塞队列
- 核心线程数已满,堵塞队列已满,创建非核心线程来执行任务(非时序关联的流程,非核心线程若空闲,并且到达存活时间,会被回收),若有空闲线程,可复用
- 核心线程已满,堵塞队列已满,非核心线程已满,任务会被拒绝执行,调用RejectedExecutionHandler
接下来我们看一下是如何创建线程的,只要是addWorker()实现
private boolean addWorker(Runnable firstTask, boolean core) {
//代码有所裁剪,只是为了可以看得更加简洁
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
//创建核心线程或者非核心线程,由core变量来控制,
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
if (workerAdded) {
//调起线程
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
总结一下
上面所做的事情就是new一个worker对象,并且调用start,如果对Thread和runnable关系比较熟悉的同学,一猜就知道t就是Thread,Wroker就是runnable的二次封装。
接下来我们解密一下Worker
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
Worker(Runnable firstTask) {
//线程状态,里面维护了线程状态,这篇文章不是将线程状态的,所以这里可以忽略掉,以后有时间可以再仔细讲讲这一块
setState(-1);
this.firstTask = firstTask;
//构造一个线程
this.thread = getThreadFactory().newThread(this);
}
/** Delegates main run loop to outer runWorker. */
public void run() {
//由thread.start()方法调用,执行到run()
runWorker(this);
}
}
接下来看一下runWorker的方法
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
boolean completedAbruptly = true;
try {
//从任务队列中不断取出任务,执行
while (task != null || (task = getTask()) != null) {
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);
}
}
这个方法所做的事情就是不断从task中取出任务来执行,那么问题来了,按照上面的分析这个线程是结束的呀(一个thread只要run执行结束,那么线程就退出了),那么核心线程是怎么保活的呢?其实所有的谜题都在getTask方法中
private Runnable getTask() {
boolean timedOut = false;
try {
//判断一下是否是核心线程,非核心线程用poll,核心线程用take,就是这两个方法做到非核心线程多少s超时,核心线程保存。
//poll方法是若队列为null,那么会等待多少时间,如果在等待时间内队列有新任务加入,那么久立即返回新任务,在这个时间后,那么就返回Null,也就是正常退出了
//take()方法是若队列为null,那么就一直堵塞,直到队列中有任务加入。所以这就实现了核心线程的保活
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
所以核心线程的保活就是通过堵塞队列来实现的,就是poll和take的奥秘,所以若线程池中没有任务,那么线程是处于堵塞状态,是有消耗cpu资源的,所以核心线程一般情况下别设置太大,一般cpu核数+1即可。当然如果是小型app,设置小点更好
总结
所以线程池的分析就这么多了,希望对大家有所帮助