读者福利
更多笔记分享
拒绝策略名称 | 描述 |
---|---|
ThreadPoolExecutor.AbortPolicy | 丢弃任务并抛出RejectedExecutionException异常。这是线程池默认的拒绝策略,在任务不能再提交的时候,抛出异常,及时反馈程序运行状态。如果是比较关键的业务,推荐使用此拒绝策略,这样子在系统不能承载更大的并发量的时候,能够及时的通过异常发现。 |
ThreadPoolExecutor.DiscardPolicy | 这种是最坑的,大家不要用这种,出了问题两眼一抹黑,坑人坑己。丢弃任务也不抛出异常。使用此策略,使我们无法发现系统的异常状态。 |
ThreadPoolExecutor.DiscardOldestPolicy | 丢弃队列最前面的任务,然后重新提交被拒绝的任务。是否要采用此种拒绝策略,还得根据实际业务是否允许丢弃老任务来认真衡量。 |
ThreadPoolExecutor.CallerRunsPolicy | 由调用线程(提交任务的线程)处理该任务。这种情况是需要让所有任务都执行完毕,也就是你的任务特别重要,重要到阻塞后让主线程去干活的地步,但是这种的需要输出错误日志,触发报警,告知开发去优化代码, 在这种情况多线程仅仅是增大吞吐量的手段,最终必须要让每个任务都执行完毕。 |
- 任务队列(workQueue)的实现也有很多种,根据实际情况配置
其他2个参数我就不讲了,作用基本上在源码解析中可以提现。
1.2 执行
ThreadPoolExecutor的execute方法:
1.2.1 任务执行入口
public void execute(Runnable command) {
// 检查任务是否为null,如果是则抛出
if (command == null)
throw new NullPointerException();
// 获取线程池的控制状态,即一个int类型的整数
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); // addWorker 这个我们后面讲详细讲
}
// 如果无法将任务添加到队列中,创建一个新的非核心线程来执行该任务
// addWorker 这个我们后面讲详细讲
else if (!addWorker(command, false))
reject(command);
}
execute向线程池中提交一个任务,其实还有一个方法,我们此处不讲了。当线程池中的线程数量小于核心线程数时,会直接创建一个新的线程来执行任务;当线程池中的线程数量已经达到核心线程数时,任务会被放入任务队列中等待执行。
当任务队列已满时,会根据当前线程池状态和拒绝策略来决定如何处理无法执行的任务。具体来说,如果线程池处于运行状态,并且任务可以被成功加入到任务队列中,那么就会返回;如果线程池已经关闭或者任务无法加入到任务队列中,就会执行拒绝策略。
1.2.2 addWorker解析
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get(); // 获取线程池的线程数量和状态控制信息
int rs = runStateOf(c); // 获取线程池的运行状态
// 如果线程池处于关闭状态且工作队列不为空,或者正在关闭,但是还有任务在工作队列中等待执行,则返回false,不添加新的工作线程
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c); // 获取线程池中的工作线程数
// 如果线程池中的工作线程数已经达到了线程池的容量限制或者达到了核心线程池大小(如果是非核心线程池则达到了最大线程池大小),则返回false,不添加新的工作线程
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 简单翻译一下 CAS操作失败,说明线程池中的工作线程数发生了变化,需要重新进行判断
}
}
// 创建新的工作线程
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); // 将新的工作线程添加到workers列表中
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock(); // 释放线程池的主锁
}
if (workerAdded) {
t.start(); // 启动新的工作线程
workerStarted = true;
}
}
} finally {
if (! workerStarted)
// 如果启动新的工作线程失败,则进行相应的处理,此处如果感兴趣,可以点进去看下
// 哈哈可以看到在线程池的实现中到处都是锁,
// 顺便也可以了解到ReentrantLock原来用的地方这么多
// 这也是多线程带来的问题,线程安全是所有实现多线程必须要考虑的事情
addWorkerFailed(w);
}
return workerStarted; // 返回是否成功启动新的工作线程
}
addWorker方法用于向线程池中添加一个新的线程,并且返回一个boolean类型的值,表示线程是否启动成功。当线程池中的线程数量已经达到最大线程数时,或者线程池已经关闭时,就无法再添加新的线程了。
当新的线程启动成功时,会将线程加入到workers集合中,同时更新线程池的最大线程数。如果线程启动失败,则会执行addWorkerFailed方法,将添加线程的操作回退。
从网上找了个图,方便大家理解
1.2.3 Worker类解析
Worker类有很多。大家在看源码的时候切记注意是在java.util.concurrent
包下,不然牛头不对马嘴就尴尬了。
/\*\*
\* Worker类继承了AbstractQueuedSynchronizer类,实现了Runnable接口。
\* 它是线程池中的一个工作线程,负责执行任务队列中的任务。
\*/
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
/\*\*
\* 这个类永远不会被序列化,但我们提供一个serialVersionUID来压制javac警告。
\*/
private static final long serialVersionUID = 6138294804551838833L;
/\*\* 这个工作线程正在运行的线程。如果工厂失败,则为null。 \*/
final Thread thread;
/\*\* 要运行的初始任务。可能为null。 \*/
Runnable firstTask;
/\*\* 每个线程的任务计数器 \*/
volatile long completedTasks;
/\*\*
\* 使用给定的第一个任务和来自ThreadFactory的线程创建Worker。
\* @param firstTask 第一个任务(如果没有则为null)
\*/
Worker(Runnable firstTask) {
// 在运行runWorker之前,禁止中断
setState(-1);
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
/\*\* 将主运行循环委托给外部的runWorker \*/
public void run() {
runWorker(this);
// == 这个源码我没有贴出来,但是也很关键,我找了个图,帮大家理解 ==
}
// 锁定方法
//
// 值0表示未锁定状态。
// 值1表示已锁定状态。
protected boolean isHeldExclusively() {
return getState() != 0;
}
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) { // 尝试获取锁
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
public void lock() { acquire(1); }
public boolean tryLock() { return tryAcquire(1); }
public void unlock() { release(1); }
public boolean isLocked() { return isHeldExclusively(); }
/\*\*
\* 如果工作线程已经启动,则中断它。
\*/
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
- 我们可以看到Worker 类实现了Runnable接口,并且继承自AbstractQueuedSynchronizer类。Worker类中的run方法会不断地从任务队列中取出任务并执行,直到线程池关闭为止。
- 在Worker类中,我们主要关注tryAcquire和tryRelease方法。tryAcquire方法用于获取独占锁,当独占锁已经被其他线程占用时,tryAcquire方法返回false;tryRelease方法用于释放独占锁。
- 在runWorker方法中,当任务队列中已经没有可执行的任务时,线程会执行workerDone方法,将当前线程从workers集合中移除,并且尝试关闭线程池。
- isHeldExclusively()、tryAcquire()、tryRelease()、lock()、tryLock()、unlock()和isLocked()这些方法是Worker实现的AbstractQueuedSynchronizer抽象类中的一些锁定方法,此处就不解析了应该不影响大家整体理解。
runWorker(this); 方法实现
虽然没有贴出来源码,但从网上找了个图帮大家理解一下
1.3 关闭
- 当线程池收到关闭命令后,线程池的状态会被设置为SHUTDOWN,此时线程池不再接受新的任务。接着,线程池会遍历任务队列中的任务,并将它们逐个取出交给线程池中的线程去执行。
- 在执行任务的过程中,如果线程池中的线程被中断,那么它们会抛出InterruptedException异常,此时需要将该异常抛出到任务的调用者处进行处理。
- 当任务队列中的任务执行完毕后,线程池中的线程会被逐个关闭,直到所有线程都关闭为止。在关闭线程池的过程中,如果任务队列中还有未执行的任务,那么这些任务将会被丢弃。
// shutdown方法,关闭线程池
public void shutdown() {
// 获取线程池的锁
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 检查是否有关闭线程池的权限
checkShutdownAccess();
// 将线程池的运行状态设置为SHUTDOWN,不再接受新任务
advanceRunState(SHUTDOWN);
// 中断处于空闲状态的工作线程
interruptIdleWorkers();
// 调用onShutdown方法,用于ScheduledThreadPoolExecutor
onShutdown();
} finally {
// 释放线程池的锁
mainLock.unlock();
}
// 尝试终止线程池
tryTerminate();
}
// interruptIdleWorkers方法,中断处于空闲状态的工作线程
private void interruptIdleWorkers() {
// 获取线程池的锁
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();
}
}
### 最后
**我还通过一些渠道整理了一些大厂真实面试主要有:蚂蚁金服、拼多多、阿里云、百度、唯品会、携程、丰巢科技、乐信、软通动力、OPPO、银盛支付、中国平安等初,中级,高级Java面试题集合,附带超详细答案,希望能帮助到大家。**
![新鲜出炉的蚂蚁金服面经,熬夜整理出来的答案,已有千人收藏](https://img-blog.csdnimg.cn/img_convert/6874244c6e71be0b37f30f8b598ec1d3.webp?x-oss-process=image/format,png)
**还有专门针对JVM、SPringBoot、SpringCloud、数据库、Linux、缓存、消息中间件、源码等相关面试题。**
![新鲜出炉的蚂蚁金服面经,熬夜整理出来的答案,已有千人收藏](https://img-blog.csdnimg.cn/img_convert/e405d0f805ba8565abec01db31b3ecda.webp?x-oss-process=image/format,png)
> **本文已被[CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)收录**
**[需要这份系统化的资料的朋友,可以点击这里获取](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)**
1257528)]
**还有专门针对JVM、SPringBoot、SpringCloud、数据库、Linux、缓存、消息中间件、源码等相关面试题。**
[外链图片转存中...(img-uVpugNXc-1715691257528)]
> **本文已被[CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)收录**
**[需要这份系统化的资料的朋友,可以点击这里获取](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)**