线程池使用
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
public class ThreadPoolExecutorTest {
/**
* 创建一个完整线程池参数
* 核心线程数为 10
* 最大线程数为 20
* 存活时间为10 分钟
* 工作队列 LinkedBlockingQueue
* 线程工厂为默认 DefaultThreadFactory
* 拒绝策略 为 AbortPolicy (直接抛异常)
*/
private static ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 20, 10, TimeUnit.MINUTES,
new LinkedBlockingQueue<Runnable>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
/**
* 创建固定大小核心线程数为5 最大线程数为5 没有超时时间 线程池 工作队列 LinkedBlockingQueue
*/
private static ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(5);
/**
* 创建核心线程数为 0 最大线程数 Integer.MAX_VALUE 存活时间为 60秒 该线程可以无限扩展 工作队列使用同步移交SynchronousQueue
*/
private static ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
/**
* 创建 核心数为 1 最大线程数为 1 没有超时时间 工作队列 LinkedBlockingQueue
*/
private static ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
/**
* 创建核心线程数为5 给定的延迟之后运行任务, 或者定期执行任务的线程池 工作队列 使用延迟队列 DelayedWorkQueue
*/
private static ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(5);
public static void main(String[] args) throws ExecutionException, InterruptedException {
/**
* 例子1: 没有返回结果的异步任务
*/
executor.submit(new Runnable() {
@Override
public void run() {
// do something
System.out.println("没有返回结果的异步任务");
}
});
/**
* 例子2: 有返回结果的异步任务
*/
Future<List<String>> future = executor.submit(new Callable<List<String>>() {
@Override
public List<String> call() {
List<String> result = new ArrayList<>();
result.add("jackJson");
return result;
}
});
List<String> result = future.get(); // 获取返回结果
System.out.println("有返回结果的异步任务: " + result);
/**
* 例子3:
* 有延迟的, 周期性执行异步任务
* 本例子为: 延迟1秒, 每2秒执行1次
*/
newScheduledThreadPool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("this is " + Thread.currentThread().getName());
}
}, 1, 2, TimeUnit.SECONDS);
/**
* 例子4: FutureTask的使用
*/
Callable<String> task = new Callable<String>() {
public String call() {
return "jackJson";
}
};
FutureTask<String> futureTo = new FutureTask<String>(task);
executor.submit(futureTo);
System.out.println(futureTo.get());
}
}
线程池是什么
线程池(Thread Pool)是一种基于池化思想管理线程的工具,经常出现在多线程服务器中,如MySQL。线程过多会带来额外的开销,其中包括创建销毁线程的开销、调度线程的开销等等,同时也降低了计算机的整体性能。线程池维护多个线程,等待监督管理者分配可并发执行的任务。这种做法,一方面避免了处理任务时创建销毁线程开销的代价,另一方面避免了线程数量膨胀导致的过分调度问题,保证了对内核的充分利用。
线程池有哪些优点
- 降低资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造成的损耗。
- 提高响应速度:任务到达时,无需等待线程创建即可立即执行。
- 提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。
- 提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。
线程池解决什么问题?
线程池解决的核心问题就是资源管理问题。在并发环境下,系统不能够确定在任意时刻中,有多少任务需要执行,有多少资源需要投入。这种不确定性将带来以下若干问题:
- 频繁申请/销毁资源和调度资源,将带来额外的消耗,可能会非常巨大。
- 对资源无限申请缺少抑制手段,易引发系统资源耗尽的风险。
- 系统无法合理管理内部的资源分布,会降低系统的稳定性。
ThreadPoolExecutor 源码解析
线程池工作流程
- 首先检测线程池运行状态,如果不是RUNNING,则直接拒绝,线程池要保证在RUNNING的状态下执行任务。
- 如果workerCount < corePoolSize,则创建并启动一个线程来执行新提交的任务。
- 如果workerCount >= corePoolSize,且线程池内的阻塞队列未满,则将任务添加到该阻塞队列中。
- 如果workerCount >= corePoolSize && workerCount <
maximumPoolSize,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务。 - 如果workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满, 则根据拒绝策略来处理该任务,默认的处理方式是直接抛异常。
在查看ThreadPoolExecutor 源码时,我们需要了解一些基础知识,以便我们更好的理解源码。
工作队列
工作队列模块是线程池能够管理任务的核心部分。线程池的本质是对任务和线程的管理,而做到这一点最关键的思想就是将任务和线程两者解耦,不让两者直接关联,才可以做后续的分配工作。线程池中是以生产者消费者模式,通过一个阻塞队列来实现的。阻塞队列缓存任务,工作线程从阻塞队列中获取任务。
阻塞队列(BlockingQueue) 是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。
- ArrayBlockingQueue:列表形式的工作队列,必须要有初始队列大小,有界队列,先进先出。
- LinkedBlockingQueue:链表形式的工作队列,可以选择设置初始队列大小,有界/无界队列,先进先出,此队列的的长度为 Integer.MAX_VALUE 默认创建该队列由容量风险。
- SynchronousQueue:SynchronousQueue不是一个真正的队列,而是一种在线程之间移交的机制。要将一个元素放入SynchronousQueue中,
必须有另一个线程正在等待接受这个元素. 如果没有线程等待,并且线程池的当前大小小于最大值,那么ThreadPoolExecutor将创建
一个线程, 否则根据饱和策略,这个任务将被拒绝。使用直接移交将更高效,因为任务会直接移交 给执行它的线程,而不是被首先放在队列中,
然后由工作者线程从队列中提取任务. 只有当线程池是无界的或者可以拒绝任务时,SynchronousQueue才有实际价值。 - PriorityBlockingQueue:优先级队列,无界队列,根据优先级来安排任务,任务的优先级是通过自然顺序或Comparator(如果任务实现了Comparator)来定义的。
- DelayedWorkQueue:延迟的工作队列,无界队列。
拒绝策略
当有界队列被填满后,饱和策略开始发挥作用。ThreadPoolExecutor的饱和策略可以通过调用setRejectedExecutionHandler来修改。(如果某个任务被提交到一个已被关闭的Executor时,也会用到饱和策略)。饱和策略有以下四种,一般使用默认的AbortPolicy。
- AbortPolicy:中止策略。默认的饱和策略,抛出未检查的RejectedExecutionException。调用者可以捕获这个异常,然后根据需求编写自己的处理代码。
- DiscardPolicy:抛弃策略。当新提交的任务无法保存到队列中等待执行时,该策略会悄悄抛弃该任务。
- DiscardOldestPolicy:抛弃最旧的策略。当新提交的任务无法保存到队列中等待执行时,则会抛弃下一个将被执行的任务,然后尝试重新提交新的任务。(如果工作队列是一个优先队列,那么“抛弃最旧的”策略将导致抛弃优先级最高的任务,因此最好不要将“抛弃最旧的”策略和优先级队列放在一起使用)。
- CallerRunsPolicy:调用者运行策略。该策略实现了一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者(调用线程池执行任务的主线程),从而降低新任务的流程。它不会在线程池的某个线程中执行新提交的任务,而是在一个调用了execute的线程中执行该任务。当线程池的所有线程都被占用,并且工作队列被填满后,下一个任务会在调用execute时在主线程中执行(调用线程池执行任务的主线程)。由于执行任务需要一定时间,因此主线程至少在一段时间内不能提交任务,从而使得工作者线程有时间来处理完正在执行的任务。在这期间,主线程不会调用accept,因此到达的请求将被保存在TCP层的队列中。如果持续过载,那么TCP层将最终发现它的请求队列被填满,因此同样会开始抛弃请求。当服务器过载后,这种过载情况会逐渐向外蔓延开来——从线程池到工作队列到应用程序再到TCP层,最终达到客户端,导致服务器在高负载下实现一种平缓的性能降低。
运行状态
RUNNING : 能接受新提交的任务,并且也能处理阻塞队列中的任务。
SHUTDOWN : 关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务。
STOP: 不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程。
TIDYING : 所有的任务都已经中止了,workerCount(有效线程数) 为0 。
TERMINATED : 在terminated() 方法执行完后进入该状态。
ThreadPoolExecutor UML 图
源码
关键属性
//这个属性是用来存放 当前运行的worker数量以及线程池状态的
//int是32位的,这里把int的高3位拿来充当线程池状态的标志位,后29位拿来充当当前运行worker的数量
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// 高3位用来表示运行状态,此值用于运行状态向左移动的位数,即29位
private static final int COUNT_BITS = Integer.SIZE - 3;
// 线程数容量,低29位表示有效的线程数, 0001 1111 1111 1111 1111 1111 1111 1111
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
//存放任务的阻塞队列
private final BlockingQueue<Runnable> workQueue;
//worker的集合,用set来存放
private final HashSet<Worker> workers = new HashSet<Worker>();
//历史达到的worker数最大值
private int largestPoolSize;
//当队列满了并且worker的数量达到maxSize的时候,执行具体的拒绝策略
private volatile RejectedExecutionHandler handler;
/**
* 线程超时时间,当线程数超过corePoolSize时生效,
* 如果有线程空闲时间超过keepAliveTime, 则会被终止
*/
private volatile long keepAliveTime;
//核心线程 worker的数量
private volatile int corePoolSize;
//最大worker的数量,一般当workQueue满了才会用到这个参数
private volatile int maximumPoolSize;
// 是否允许核心线程超时,默认false,false情况下核心线程会一直存活。
private volatile boolean allowCoreThreadTimeOut;
线程池运行状态
// 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;
private static final int TERMINATED = 3 << COUNT_BITS;
- RUNNING: -1 << COUNT_BITS,即高3位为111,该状态的线程池会接收新任务,并处理阻塞队列中的任务;
- SHUTDOWN: 0 << COUNT_BITS,即高3位为000,该状态的线程池不会接收新任务,但会处理阻塞队列中的任务;
- STOP : 1 <<COUNT_BITS,即高3位为001,该状态的线程不会接收新任务,也不会处理阻塞队列中的任务,而且会中断正在运行的任务;
- TIDYING : 2 << COUNT_BITS,即高3位为010, 所有的任务都已经终止;
- TERMINATED: 3 << COUNT_BITS,即高3位为011, terminated()方法已经执行完成
execute 方法
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
int c = ctl.get();
/**
**1.workerCountOf获取线程池的当前线程数;
**2.小于corePoolSize,执行addWorker()创建新线程(核心线程)执行command任务
*/
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); //调用拒绝策略
//再次检查时,如果有效的线程数为0,
else if (workerCountOf(recheck) == 0)
addWorker(null, false); // 则新建一个线程(非核心线程)
}
//走到这里说明阻塞队列已经满了
//尝试新建一个线程来处理任务(非核心)
else if (!addWorker(command, false))
reject(command); //创建失败 执行拒绝策略
}
该方法对应上面线程池工作流流程。
addWorker 方法
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c); //获取线程池状态
// Check if queue empty only if necessary.
//1).rs为RUNNING,通过校验。
// 2).rs为STOP或TIDYING或TERMINATED,返回false。(STOP、TIDYING、TERMINATED:已经停止进入最后清理终止,不接受任务不处理队列任务)
// 3).rs为SHUTDOWN,提交的任务不为空,返回false。(SHUTDOWN:不接受任务但是处理队列任务,因此任务不为空返回false)
// 4).rs为SHUTDOWN,提交的任务为空,并且工作队列为空,返回false。 (状态为SHUTDOWN、提交的任务为空、工作队列为空,则线程池有资格关闭,直接返回false)
//5).rs为SHUTDOWN,提交的任务为空,并且工作队列不为空,通过校验。(因为SHUTDOWN状态下刚好可以处理队列任务)
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c); //获取当前线程池数量
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize)) // 线程池数量超过阈值 这返回false
return false;
if (compareAndIncrementWorkerCount(c)) // 使用CAS 对当前线程数+1
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs) //走到这里说明CAS 增加线程数失败 判断当前线程状态是否发生变化
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); 通过提交的任务创建worker
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()); //再次获取线程池状态
//1.当前线程池状态为RUNNING 校验通过
//2.当前线程池状态为SHUTDOWN 且当前提交任务为空 校验通过
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable //预先校验线程是否可以启动
throw new IllegalThreadStateException();
workers.add(w); //加入worker 集合中
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) { //work 创建成功 启动线程
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted) //启动线程 失败
addWorkerFailed(w);//移除添加的worker 当前线程数减1,因为之前校验通过是加1过.
}
return workerStarted;
}
addWork方法主要通过 for循环校验当前线程状态,使用CAS 增加当前线程数,提交的 firstTask 创建worker,线程启动时,会调用Worker里的run方法,执行runWorker(this)方法下面会介绍这个方法。
addWorkerFailed 方法
private void addWorkerFailed(Worker w) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (w != null)
workers.remove(w); //移除worker
decrementWorkerCount();//当前线程数减 1
tryTerminate();//尝试中止线程
} finally {
mainLock.unlock();
}
}
该方法很简单,就是移除入参中的Worker并将workerCount-1,最后调用tryTerminate尝试终止线程池,tryTerminate见下文对应方法源码解读
runWorker 方法
public void run() {
runWorker(this);
}
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();//获取当前线程
Runnable task = w.firstTask;//获取worker 中的任务
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
//当前任务不为空 或者 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
//注释翻译如下:
//如果池正在停止,请确保线程被中断;如果没有,请确保线程不被中断。这第二种情况需要重新检查才能处理shutdown清除中断时无竞争
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt(); //程池的状态为停止并且wt不为中断, 则将wt中断
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++; //完成任务数加1
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
该方法为Worker线程开始执行任务,首先执行当初创建Worker时的初始任务,接着从工作队列中获取任务执行。主要涉及两个方法:获取任务的方法getTask(见下文getTask源码解读)和执行Worker退出的方法processWorkerExit(见下文processWorkerExit源码解读)。注:processWorkerExit在处理正常Worker退出时,没有对workerCount-1,而是在getTask方法中进行workerCount-1。
getTask 方法
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.
//1.如果线程池状态不是RUNNING
//2.当前线程为SHUTDOWN 且 队列为空 则需要移除当前worker
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount(); //移除当前worker 将workerCount -1
return null;
}
int wc = workerCountOf(c); //获取当前线程数量
// Are workers subject to culling?
//如果allowCoreThreadTimeOut为true,或者workerCount大于核心线程数
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
// 如果wc超过最大线程数 或者 当前线程会超时并且已经超时,
// 并且wc > 1 或者 工作队列为空,则返回null,代表当前Worker需要移除
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
// 返回null前将workerCount - 1,
// 因此processWorkerExit中completedAbruptly=false时无需再减
return null;
continue;
}
try {
//根据上面的判断 wc > corePoolSize 当前线程数大于核心线程数,会调用poll 如果小于核心线程数,会调用take() 进行阻塞 直到获取到任务 这里也使得是核心线程数保活的关键
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
Worker从工作队列获取任务,如果allowCoreThreadTimeOut为false并且 workerCount<=corePoolSize,则这些核心线程永远存活,并且一直在尝试获取工作队列的任务;否则workerCount>corePoolSize或者allowCoreThreadTimeOut=true ,当在keepAliveTime时间内获取不到任务,该线程的Worker会被移除。
Worker移除的过程:getTask方法返回null,导致runWorker方法中跳出while循环,调用processWorkerExit方法将Worker移除。注意:在返回null的之前,已经将workerCount-1,因此在processWorkerExit中,completedAbruptly=false的情况(即正常超时退出)不需要再将workerCount-1。
processWorkerExit 方法
private void processWorkerExit(Worker w, boolean completedAbruptly) {
//1.如果worker是异常死亡,则completedAbruptly =true 会将workerCount-1
//2. completedAbruptly = false 则表示getTask() 返回null,在getTask() 中已经减过 1
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();//加锁
try {
completedTaskCount += w.completedTasks;//统计完成任务数
workers.remove(w);//移除worker
} finally {
mainLock.unlock();
}
tryTerminate();//尝试终止线程池
int c = ctl.get();
if (runStateLessThan(c, STOP)) {//如果当前线程池没有停止(线程池状态为RUNNING ,SHUNTDOWN)
if (!completedAbruptly) {//worker 不是异常死亡
// min为线程池的理论最小线程数:如果允许核心线程超时则min为0,否则min为核心线程数
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
//如果最小线程数为0 且队列不为空 将最小线程数设置为1
if (min == 0 && ! workQueue.isEmpty())
min = 1;
// 如果当前线程数大于最小线程数,直接return
if (workerCountOf(c) >= min)
return; // replacement not needed
}
/ 如果代码走到这边,代表workerCountOf(c) < min,此时会走到下面的addWorker方法。
// 通过getTask方法我们知道,当allowCoreThreadTimeOut为false
// 并且workerCount<=corePoolSize时,是不会走到processWorkerExit方法的。
// 因此走到这边只可能是当前移除的Worker是最后一个Worker,但是此时工作
// 队列还不为空,因此min被设置成了1,所以需要在添加一个Worker来处理工作队列。
addWorker(null, false);
}
}
tryTerminate 方法
final void tryTerminate() { // 尝试终止线程池
for (;;) {
int c = ctl.get();
// 只有当前状态为STOP 或者 SHUTDOWN并且队列为空,才会尝试整理并终止
// 1: 当前状态为RUNNING,则不尝试终止,直接返回
// 2: 当前状态为TIDYING或TERMINATED,代表有其他线程正在执行终止,直接返回
// 3: 当前状态为SHUTDOWN 并且 workQueue不为空,则不尝试终止,直接返回
if (isRunning(c) || // 1
runStateAtLeast(c, TIDYING) || // 2
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty())) // 3
return;
// 走到这代表线程池可以终止(通过上面的校验)
// 如果此时有效线程数不为0, 将中断一个空闲的Worker,以确保关闭信号传播
if (workerCountOf(c) != 0) { // Eligible to terminate
interruptIdleWorkers(ONLY_ONE);
return;
}
final ReentrantLock mainLock = this.mainLock;
mainLock.lock(); // 加锁,终止线程池
try {
// 使用CAS将ctl的运行状态设置为TIDYING,有效线程数设置为0
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
terminated(); // 供用户重写的terminated方法,默认为空
} finally {
// 将ctl的运行状态设置为TERMINATED,有效线程数设置为0
ctl.set(ctlOf(TERMINATED, 0));
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
// else retry on failed CAS
}
}
线程池如何调优
- 选择合适的线程池大小,过小的线程池可能会导致任务一直在排队, 过大的线程池可能会导致大家都在竞争 CPU 资源,增加上下文切换的开销 可以根据业务是 IO密集型还是 CPU 密集型来选择线程池大小:
CPU 密集型:指的是任务主要使用来进行大量的计算,没有什么导致线程阻塞。一般这种场景的线程数设置为 CPU 核心数+1。
IO 密集型:当执行任务需要大量的 io,比如磁盘 io,网络 io,可能会存在大量的阻塞,所以在 IO 密集型任务中使用多线程可以大大地加速任务的处理。一般线程数设置为 2*CPU 核心数。 - 任务队列的选择 :使用有界队列可以避免资源耗尽的风险,但是可能会导致任务被拒绝。 使用无界队列虽然可以避免任务被拒绝,但是可能会导致内存耗尽。一般需要设置有界队列的大小,比如 LinkedBlockingQueue。在构造的时候可以传入参数来限制队列中任务数据的大小,这样就不会因为无限往队列中扔任务导致系统的 oom。
- 尽量使用自定义的线程池,而不是使用 Executors 创建的线程池,因为 newFixedThreadPool 线程池由于使用了LinkedBlockingQueue,队列的容量默认无限大,实际使用中出现任务过多时会导致内存溢出;newCachedThreadPool 线程池由于核心线程数无限大,当任务过多的时候会导致创建大量的线程,可能机器负载过高导致服务宕机。
优化方案参考