创作的心路历程:
一直都想对多线程进行一个全面的了解,但是内部分享直接对着代码进行分析不太友好。
所以就寻找一个切入点 就找到了 线程池(不细讲锁类型和Queue,专门开篇讲)。
线程池
- 都了解或使用过
- 包含 重量级锁 轻量级 类似自旋锁
- 多线程路上的基石
正文
带着问题 进行分析容易迷失代码中。
- 准备基础知识
- 各类线程启动方式区别?
- 线程池必备的基础知识点。
重点探究的:
- 线程池中的线程 如何复用?
- 线程池中的线程 什么时候开始 run?
- 什么时候创建非核心线程的?
- 基本配置下核心线程 为什么不会被回收,什么情况下会?
- 动手配置线程池参数有哪些坑?
- 有界和无界引发的问题。
- 自定义Factory 线程优化的场景。
开始
一段简单的 simple
ExecutorService executorService = Executors.xxxx();
newFixedThreadPool.execute(new runnable(){
run(){
// todo
}
});
带着上面疑问去看
那我们进入 execute
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
// 返回 包含线程数及线程池状态的Integer类型 的值
int c = ctl.get();
// 如果工作线程数小于核心线程数 ,则创建线程任务并执行
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
//## 举例说明什么时候添加失败。 添加失败的话,防止外部都已经在线程池中加入新任务,重新获取 检查一下。
c = ctl.get();
}
// 如果在 running 状态下,把 command 置于 提供的数据结构中。
// RUNNING: Accept new tasks and process queued tasks
// 第一部分
if (isRunning(c) && workQueue.offer(command)) {
// 重新检查一下
int recheck = ctl.get();
// 如果不是 running 状态下, 把之前加入队列的任务移出
// 什么情况下成立 答:非runnig 状态 shotdown 之类的等。
if (!isRunning(recheck) && remove(command))
reject(command);
// 工作线程消费完毕,重新添加。
else if (workerCountOf(recheck) == 0)
// 第二部分
addWorker(null, false);
// 第三部分
} else if (!addWorker(command, false)) // 第四部分
// 如果添加失败就拒绝策略
reject(command);
}
解决了: 什么时候创建非核心线程的?
当我们读懂这部分代码的时候,应该会有这些疑问。
当 workerCountOf© < corePoolSize == false下
疑问一:
第一部分 通过的情况下,当 if else if 都不满足的情况下。
参数runnable 怎么执行?
疑问二:
第四部分,怎么去理解?
先继续查看
* firstTask 外部启动线程池时需要构造第一个线程,它是线程的母体。
* core 新增工作线程时判断的指标:
* true 表示新增工作线程时,需要判断当前的running状态的线程 是否少于 corePoolSize
* false 新增工作线程时,需要判断当前runnig 状态的线程是否少于maximumPoolSize
*
*
* runnig < shutDown < stop < tidying < terminated
* <p>
* (runnig) 线程池 能接受新任务
* < (shutDown)不在接收新任务,但可以继续执行队列中的任务
* < (stop) 全面拒绝,并中断正在处理的任务
* < (tidying) 所有任务已经被终止
* < (terminated) 已经清理完毕。
*/
private boolean addWorker(Runnable firstTask, boolean core) {
// 快速退出多层嵌套。
retry:
for (; ; ) {
// 返回 包含线程数及线程池状态的Integer类型 的值
int c = ctl.get();
// 获取线程池 当前的状态.
int rs = runStateOf(c);
/**
* ## 拒绝接收 新的任务了。消耗队内的任务。
*/
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN &&
!(rs == SHUTDOWN &&
firstTask == null &&
!workQueue.isEmpty()))
return false;
for (; ; ) {
// 工作线程数量
int wc = workerCountOf(c);
// 如果超过最大线程数 则不能添加新线程。
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
/**
* # 类似 自旋锁 #
* 原理就是
* 先逻辑 + 1 创建失败 再减1 轻量级处理并发 ,
* 如果先创建线程,代价远比这个大。
* 如果这个地方失败了 就是说明其他线程也在竞争锁
*/
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 {
// 封装工作线程 为 Worker 对象。
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
// 在进行ThreadPoolExecutor的敏感操作性
// 都需要持有主锁(全局锁),避免添加和启动被干扰
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());
/**
* rs == SHUTDOWN 不在接收新的任务。 会继续等待队列中的任务。
*/
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
// worker 里面的 thread 可不能是已经启动的
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
// 满足条件 计入 工作队列中。
workers.add(w);
int s = workers.size();
// largestPoolSize 用于记录 workers 中的个数的最大值
// 因为 workers 是不断增加减少的,通过这个值可以知道线程池的大小曾经达到的最大值
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
// 添加成功 启动线程
// 并不是直接启动。
t.start();
workerStarted = true;
}
}
} finally {
// 如果添加失败 再把之前添加减回去.
if (!workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
上面代码主要就是 检测 看是否能添加Worker,如果没有任何问题,就开始start.
并不能解决,上一个代码块 留下来的问题。
继续向下看:
t.start(); 的时候,我们应该要找到 Runnable.run()
点开 worker方法,就可以看到创建线程的时候 指向了 Worker 的 run 方法.
Worker(Runnable firstTask) {
/**
* AbstractQueuedSynchronizer 禁止线程中断。
*/
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);
}
这算是真正走上运行的道路了。算是这次重点。
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 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 {
//运行起来了
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();
}
}
// worker.size == 0 || error 情况下
completedAbruptly = false;
} finally {
/**
* 1. woker == null 没有事做了
* 2. 异常情况
*/
processWorkerExit(w, completedAbruptly);
}
}
描述一下:
如果是第一个execute()
task 必然有值,按照这个思路下去。
进行检测 是否还在 运行中 ,线程的状态。如果通过 就run 起来。
解决上面那些问题:
(疑问一) && (线程池中的线程 什么时候开始 run)
问题有来了,还有那些问题在哪里可以找到答案了。
出现这个情况的 我们就带着疑问一(全局搜索一下它) 和 上面的问题来进行探索 来进行探索。
* worker 退出的原因:
* 1.有多个maximumPoolSize工作者(由于*调用setMaximumPoolSize)
* 2.线程池 stop
* 3.rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())
*/
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.
// 看 等级排列
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
// cas 操作 减少工作线程数
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
/**
* 核心 和非核心 在 allowCoreThreadTimeOut =false 情况下
*
* 非核心 有超时时间。
*/
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
//
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
/**
* timed = true
* 不考虑 allowCoreThreadTimeOut =true 情况下
* wc > corePoolSize 有 非核心线程
* 所有执行 workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)
* 有时间限制的阻塞线程
* 反之:
* 核心线程 一直阻塞
* take() 注释描述 等待 一直等待到可用数据。
*/
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
/**
* 这异常很明确, 线程被打断。
* 动态设置 setMaximumPoolSize 重试
* 保护措施 相当好
*/
timedOut = false;
}
}
}
更多的已经在注释当中写了,相当明确
基本已经结束:
解答一下问题:
- 线程池中的线程 如何复用?
代码块 1:
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
理解就是好好的利用Thread.start() 从而对 runnable 一个替换移除。
- 基本配置下核心线程 为什么不会被回收,什么情况下会?
代码块 2:
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
我所说的基本情况下 是 allowCoreThreadTimeOut=false
代码块 1 , 代码块 2 ,runWorker() ,一起看并且了解 BlockingQueue。
当线程总数超出了核心线程 ,其实就是有非核心线程了,如果非核心线程超过了
keepAliveTime 这个时间 没有获取到 任务。 就返回 null。 这个时候 runWorker() 循环就终止,
进入 processWorkerExit() 线程被移除了。
如果没有超过的或 就会一直执行 workQueue.take(); 一直阻塞,等到取出数据.