目录
前言
面对资源限制,为了更好的掌握充分利用有限的资源,所以有很多的池化技术,比如对象池,线程池,连接池等等,主要是用来更好的利用和控制池子里的资源,不至于线程过多导致系统内存,cpu,网络资源不足,过少又浪费cpu性能。
之前的一篇
了解线程池中,主要是了解了线程池的使用和运行流程,此篇再次回顾线程池的工作原理及源码解析。
1、使用示例
首先来看一个平时经常用的线程池Demo:
class ThreadExecuteRunTask implements Runnable {
@Override
public void run() {
try {
sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
ExecutorService threadPool = new ThreadPoolExecutor(5, 10, 20, TimeUnit.MINUTES,
new LinkedBlockingQueue<Runnable>(1000));
ThreadExecuteRunTask task = new ThreadExecuteRunTask();
threadPool.execute(task); //思考:只是传入了参数2和任务实现类task,并没有创建一个新的线程;为什么??
}
问题思考:我们只是传入了task任务类,并没有创建新的线程,然后我们的任务就被执行了,这其中的过程是怎么样的呢?
对于我们平时正常使用线程池的流程如下:
//第一步:创建线程,指定线程任务;
Thread thread = new Thread(new ThreadExecuteRunTask());
//第二步:启动线程
thread.start();
但是使用了线程池,就不必这样做了。
创建一个线程就不必了,线程的创建和销毁管理,任务的运行统统交给了线程池,通过线程池里的线程直接调用了task里的run方法,从而控制了线程的数量,也让线程的创建和任务的执行解耦。
2、线程池的简单实现
下面写了一段伪代码来代表线程池的实现原理,大致类似如下过程:
//实际类似:
1、传入参数2,线程池就创建了2个常驻核心线程work1和work2;
corePoolSize = 2;
ExecutorService messageHandlerPool = new ThreadPoolExecutor(2, 5, 20, TimeUnit.MINUTES,
new LinkedBlockingQueue<Runnable>(1000));
//预先创建2个worker线程
Worker1
run(){
task.run(); //方法的调用,并不是创建新的线程来执行
}
worker1.start()
Worker2
run(){
task.run();
}
worker12.start()
2、提交任务后,通过创建的线程worder调用其run方法执行了,并没有创建新的线程,从而控制了线程的数量;
threadPool.execute(new ThreadExecuteRunTask());
这里实现常驻2个线程,运行无数的任务:
这里通过线程池里的常驻线程调用了task里的任务run()方法执行完毕,所以这里也没有创建新的线程,达到了控制线程数量的目的。示意图如下:

思考:那当任务量比较多的时候怎么办呢?
当所有的核心worker线程都在努力工作的时候,没有空闲,之后的任务就需要被延迟执行,这时候加上了阻塞队列ArrayBolockingQueue来暂时缓存这些任务,达到了一个流控的作用;如果队列里缓存也达到最大,则再创建不大于MaxSize的线程帮助工作,下面是一个简单模拟了线程池工作流程的实现:
public class ThredPoolSample {
static ArrayBlockingQueue abq = new ArrayBlockingQueue(10);
public void init() {
new Thread(() -> {
while (true) { //创建一个线程,永远去等待执行任务;
try {
Task task = (Task) abq.take(); //阻塞获取任务
task.run(); //核心:调用任务的run方法而非创建线程;
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
public static boolean sendData() {
try {
abq.put(new Task()); //阻塞发送数据
} catch (InterruptedException e) {
e.printStackTrace();
return false;
}
return true;
}
public static class Task implements Runnable {
@Override
public void run() {
System.out.println("do task");
}
}
}
public static void main(String[] args) {
sendData(); //向队列里发送数据;
}
}
好了,简单了解完大致的流程后,我们来分析一下JDK中的线程池是如何实现的。
3、线程池的实现原理
线程池的执行流程之前有分析过,这里不再赘述了,贴张图吧。

4、源码分析
下面来看一下线程池源码的具体实现:
4.1、execute()
从提交任务入口excute执行方法开始:
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);
}
ctl解析:
-
高三位:代表当前线程池中线程的状态,用于控制线程的执行,比如通过FutureTask都是通过这个状态来获取线程的值;
-
低29位:表示线程的数量;
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
@Native public static final int SIZE = 32;
//利用ctl的高三位表示线程的运行状态,低29位表示线程的数量;
private static final int COUNT_BITS = Integer.SIZE - 3;
// runState is stored in the high-order bits
//线程池中线程的状态,用高三位表示
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; //所有的任务已经结束,即将调用线程池terminal方法
private static final int TERMINATED = 3 << COUNT_BITS; //方法执行完毕
4.2、addWork()
addWork主要功能是
创建
添加一个线程任务到线程池,
其实就做两件事情
:
-
创建一个线程并添加线程池中,可以发现线程是放在HashSet集合中的;
-
添加线程数,通过csa机制去管理记录线程数及其状态;
/**
* Set containing all worker threads in pool. Accessed only when
* holding mainLock.
*/
//Work的存储,存储在一个HashSet中:
private final HashSet<Worker> workers = new HashSet<Worker>();
private boolean addWorker(Runnable firstTask, boolean core) {
retry: //goto 语句,两个自旋,一个breaak只能跳出一层循环,所以使用goto语句;
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
//工作一:判断是否可以添加线程,
//线程池处于SHUTDOWN状态
//第一个判断:如果还要添加新任务,拒绝;
//第二个判断:线程池处于SHUTDOWN状态,不允许接受新任务,但是会继续执行队列里的任务,如果队列里任务不为空,允许创建新线程;
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) { //工作二:自旋 - 创建线程
int wc = workerCountOf(c); //得到work工作线程的数量
if (wc >= CAPACITY || //工作线程大于默认的线程数量
wc >= (core ? corePoolSize : maximumPoolSize)) //工作线程大于核心的线程数量;返回false,不允许添加线程
return false;
if (compareAndIncrementWorkerCount(c)) //工作三:通过cas来增加线程的数量
break retry;
c = ctl.get(); // Re-read ctl //再次获取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 {
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());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w); //将心创建的线程添加到set集合中
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;
}
4.3、任务Work的类结构
作用:Worker就是线程中常驻的工作线程,用于循环执行传入的任务;
思考:线程池的runWorker为啥不支持锁的重入而需要独占,为啥要重新实现AQS?
-
因为线程在执行任务的时候,不允许重入。
-
第一:获取锁lock,表示正在执行任务,任务只能被一个线程执行,不能重复执行;
-
第二:线程shutdown关闭时候,通过 lock()去判断当前的线程池是否空闲,因为不能终止正在运行的线程否则将出现数据一致性问题;
-
-
线程互斥锁的状态,state:
-
-1: 初始化状态
-
0: 释放锁
-
1:获得锁
-
//Work的实现:
private final class Worker
extends AbstractQueuedSynchronizer //思考:为什么要继承AQS,?? 避免了重入锁,实现同步阻塞
implements Runnable
{
/** Thread this worker is running in. Null if factory fails. */
final Thread thread; //真正执行任务的线程
/** Initial task to run. Possibly null. */
Runnable firstTask; //要执行的任务
/** Per-thread task counter */
volatile long completedTasks; //完成任务数,用于线程池的统计
/**
* Creates with given first task and thread from ThreadFactory.
* @param firstTask the first task (null if none)
*/
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker//初始化锁:-1
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this); //第一步:创建新线程,并且把当前的this-Worker的实现当为参数传进去;
}
/** Delegates main run loop to outer runWorker */
public void run() {
runWorker(this); //第二步:执行run方法,Worker本身实现了Runnable接口;
}
protected boolean isHeldExclusively() {
return getState() != 0;
}
//重写AQS的获取锁的逻辑,不支持重入锁 ,获取锁 1
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
//重写AQS的释放锁逻辑,没有重入锁 释放锁:0
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
线程的创建与执行:
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
/** Delegates main run loop to outer runWorker */
public void run() {
runWorker(this);
}
4.4、runWorker()
主要作用,不断的去执行我们的任务,执行runWorker()三部曲:
-
前置:beforeExecute();
-
执行:task.run(); //这里很重要,实现了线程的复用,直接调用的任务里的run()方法;
-
后置:afterExecute();
runWork具体执行步骤:
-
重新写了常驻线程的run方法,真正的去执行一个任务;
-
如果task不为空,则调用task.run()方法执行task;
-
如果task为空,通过getTask()循环去取任务,并赋值给task。若取到的Runnable不为空,则执行该任务;
-
执行完毕后,通过while循环继续getTask()取任务;
-
如果getTask()取到的任务依然是空,那么整个runWorker()方法执行完毕;
思考:
线程池如何实现线程的复用?
-
当我们运行一个Worker的时候,也就是线程池里的线程,它启动 run方法的时候,会调用 runWorker(),之后会调用 getTask()在阻塞队列里 poll任务,获取任务后并不是启动一个线程来执行,而是通过调用任务里面的 run方法来执行任务,所以可以达到重用的目的;
-
具体流程及步骤: Worker->run()->runWorker()->while(true)-getTask()->task.run()
-
run()方法只是去运行task的run方法,并没有创建新的start()线程,实现了线程的复用;
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 == 开篇提到的 task.run()
//核心:在这个while循环里实现了线程的复用;如果task为空,则调用getTask到队列里去取任务;
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
//如果线程的状态处于stop,则需要保证不接受新的任务,不执行队列里的任务,中断正在执行的任务;
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
//务执行之前;这里默认是没有实现的,我们可以重写做一些事情,比如统计监控的计时操作;
beforeExecute(wt, task);
Throwable thrown = null;
try {
//开始执行任务中的run方法,这里很重要,我们为啥传入一个任务无需启动创建一个线程?
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依然为空,通过getTask来取)+ 记录worker完成的数量 + 解锁
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
//线程的回收:当线程在超时时间内没有获取到任务,或者满足了回收的条件,就将空闲的worker移除Hashset集合;
processWorkerExit(w, completedAbruptly);
}
}
线程的回收
getTask方法返回null时,在runWorker方法中会跳出while循环,然后会执行processWorkerExit方法,销毁工作线程。
这里也是注意到了线程池的严谨之处,销毁线程的时候都是互斥锁进行判断,不可终止一个正在运行的线程,这也是线程池自己实现AQS的原因。
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.remove(w); //删除一个任务,当run方法执行完毕后,线程由JVM自动回收
} finally {
mainLock.unlock(); //解锁
}
tryTerminate();
int c = ctl.get();
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.5、getTask()
getTask() 主要做两件事:
-
获取任务:队列里循环获取下一个任务,这里也是实现线程复用的关键;
-
回收非核心线程:当线程在等待的时间内没有获取到任务,回收设置了允许回收的空闲线程;
思考:线程的回收条件是什么?
remove(
runWorker())
-
条件一:从源码我们可以看到只要线程在设置的 keepalive超时时间内,通过 workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)没有获取到任务就会被回收掉;
-
条件二:非核心线程自己运行结束,由JVM自动回收;
线程池容量设置
ThreadPoolExecutor提供了动态调整线程池容量大小的方法:
-
setCorePoolSize() : 设置核心线程池的大小
-
setMaximumPoolSize() : 设置线程池最大能创建线程数目的大小;
-
allowCoreThreadTimeOut(true); 表示核心线程数也可以被回收;
源码分析:
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.
//如果线程池的状态为SHUTDOWN,队列为空的情况,线程run执行完了怎么办?只能销毁,被JVM回收了
//情况一:线程的状态为SHUTDOWN,拒绝接受新任务 + 执行完队列里的任务;
//情况二:线程的状态为SHUTDOWNNOW,立即中断正在执行的线程,队列里的任务不会执行;
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null; //返回null,则当前的线程会退出,跳出runWorker的循环
}
int wc = workerCountOf(c);
// Are workers subject to culling?
//检查核心线程是否配置了允许超时的时间,进行超时控制;
//情况一:allowCoreThreadTimeOut:核心线程不允许超时,但是可以设置;
//情况二:corePoolSize:大于核心线程数量的线程,需要进行超时控制;
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
Runnable r = timed ?
//配置了超时时间keepAliveTime 或者当前的线程数大于核心线程数,通过poll来进行超时控制,超时还没有数据,返回null
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
//没有配置超时时间 或者 当前的线程数小于核心线程数,通过take()阻塞获取,直到有数据;
workQueue.take();
if (r != null)
return r; //如果拿到的任务不为空,则返回给线程处理
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
4.6、关闭线程池
思考:
线程池是如何保证不能中断一个正在执行的线程的?
方案:通过中断和锁机制来保护线程的执行完整的流程:
-
方式一:线程的状态为SHUTDOWN,拒绝接受新任务 + 执行完队列里的任务;
-
方式二:线程的状态为SHUTDOWNNOW,立即中断正在执行的线程,类似于命令:kill -9;队列里的任务不会执行;
推荐方式一优雅的实现关闭线程:shutdown():
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(SHUTDOWN);
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
private void interruptIdleWorkers() {
interruptIdleWorkers(false);
}
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();
}
}
4.7、线程的监控
通过runWorker()方法的分析我们知道,befoerExecutor和afterExecutor都是抽象的模版方法,所以我们可以自己去实现,做一些想做的事情。
/**
* 任务执行之前,记录任务开始时间
*/
@Override
protected void beforeExecute(Thread t, Runnable r) {
startTimes.put(String.valueOf(r.hashCode()), new Date());
}
/**
* 任务执行之后,计算任务结束时间
*/
@Override
protected void afterExecute(Runnable r, Throwable t) {
Date startDate = startTimes.remove(String.valueOf(r.hashCode())); //todo 获取的同时需要删除,否则容易引起内存的泄漏;
Date finishDate = new Date();
long diff = finishDate.getTime() - startDate.getTime();
// 统计任务耗时、初始线程数、核心线程数、正在执行的任务数量、
// 已完成任务数量、任务总数、队列里缓存的任务数量、池中存在的最大线程数、
// 最大允许的线程数、线程空闲时间、线程池是否关闭、线程池是否终止
LOGGER.info("{}-pool-monitor: " +
"Duration: {} ms, PoolSize: {}, CorePoolSize: {}, Active: {}, " +
"Completed: {}, Task: {}, Queue: {}, LargestPoolSize: {}, " +
"MaximumPoolSize: {}, KeepAliveTime: {}, isShutdown: {}, isTerminated: {}",
this.poolName,
diff, this.getPoolSize(), this.getCorePoolSize(), this.getActiveCount(),
this.getCompletedTaskCount(), this.getTaskCount(), this.getQueue().size(), this.getLargestPoolSize(),
this.getMaximumPoolSize(), this.getKeepAliveTime(TimeUnit.MILLISECONDS), this.isShutdown(), this.isTerminated());
}
至此,我们已经分析完了一个工作线程从创建到销毁的全流程了。
5、小结
线程池的核心流程涉及的方法:
-
1、executor: //执行流程
-
2、addWorker() : //创建一个线程加入到线程池;
-
3、runWorker(): //执行一个线程任务;
-
3.1、beforeExecutor()
-
3.2、task.run() //执行run方法
-
3.3、afterExecutor()
-
-
4、getTask() //获取任务
-
5、shutdown() //线程关闭
OK----君子死知己,提剑出燕京。
水滴石穿,积少成多。学习笔记,内容简单,用于复习,梳理巩固。