创建线程池-适用线程池-关闭线程池
ThreadPoolExecutor pool = new ThreadPoolExecutor(
2,//核心线程数
4,// 总线程数
10,// 救急线程存活时间
TimeUnit.SECONDS,//救急线程存活时间的单位
new LinkedBlockingDeque<>(10),//阻塞队列
Executors.defaultThreadFactory(),// 线程工厂
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);
for (int i = 0; i < 10; i++) {
pool.execute(() -> {
System.out.println(Thread.currentThread().getName() + " is executing task");
try {
Thread.sleep(1000); // 模拟任务执行
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 关闭线程池
pool.shutdown();
try {
if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
pool.shutdownNow();
}
} catch (InterruptedException e) {
pool.shutdownNow();
}
}
ThreadPoolExecutor 构造函数
主要是进行核心参数的设置
public ThreadPoolExecutor(int var1, int var2, long var3, TimeUnit var5, BlockingQueue<Runnable> var6, ThreadFactory var7, RejectedExecutionHandler var8) {
// ctl 这是一个 AtomicInteger 类型的变量,存储了线程池的状态, 还存储当前线程池中线程数的大小
this.ctl = new AtomicInteger(ctlOf(-536870912, 0));
// mainLock 这是一个 ReentrantLock 类型的锁,用于保护线程池的状态。
this.mainLock = new ReentrantLock();
// workers 这是一个 HashSet 类型的集合,用于存储工作线程。
this.workers = new HashSet();
// termination 这是一个 Condition 类型的条件变量,用于线程池的终止操作
this.termination = this.mainLock.newCondition();
if (var1 >= 0 && var2 > 0 && var2 >= var1 && var3 >= 0L) {
if (var6 != null && var7 != null && var8 != null) {
// AccessControlContext 是 Java 安全管理器(Security Manager)的一部分,用于保存当前线程的访问控制上下文。
this.acc = System.getSecurityManager() == null ? null : AccessController.getContext();
// 核心线程数
this.corePoolSize = var1;
// 最大线程数
this.maximumPoolSize = var2;
// 阻塞队列/工作队列
this.workQueue = var6;
// 救急线程的存活时间
this.keepAliveTime = var5.toNanos(var3);
// 线程工厂
this.threadFactory = var7;
// 拒绝策略
this.handler = var8;
} else {
throw new NullPointerException();
}
} else {
throw new IllegalArgumentException();
}
}
可以见得,在构造器中,还有一个特殊的属性 acc
execute(Runnable var1)
用于提交一个任务到线程池中去执行
public void execute(Runnable var1) {
if (var1 == null) {
throw new NullPointerException();
} else {
// 获取当前线程池的状态码
int var2 = this.ctl.get();
// workerCountOf(var2) 获取线程池中线程的数量
// 如果当前线程数 < 核心线程数 则会调用addWorker()方法去添加一个新的工作线程来执行任务
if (workerCountOf(var2) < this.corePoolSize) {
if (this.addWorker(var1, true)) {
return;
}
// 执行完任务获取当前线程池状态码
var2 = this.ctl.get();
}
//到这是 当前线程数 > 核心线程数
// isRunning() 检查线程池是否正在运行
// workQueue.offer(var1) 将任务添加 阻塞队列中
if (isRunning(var2) && this.workQueue.offer(var1)) {
// 当前线程正在运行 且 阻塞队列未满 当前任务成功添加到阻塞队列中
int var3 = this.ctl.get();
// 判断线程池状态 如果不是运行时状态了 把刚刚添加的任务移除,走拒绝策略
if (!isRunning(var3) && this.remove(var1)) {
this.reject(var1);
} else if (workerCountOf(var3) == 0) { // 如果线程池空了,会创建一个新的线程去等待任务
this.addWorker((Runnable)null, false);
}
// 如果线程池不在运行状态 则会去添加救急线程去处理任务, 添加成功处理任务成功直接返回
} else if (!this.addWorker(var1, false)) {
// 如果 添加工作线程失败(当前线程数 不小于 最大线程数) 无法创建救急线程
// 执行拒绝策略
this.reject(var1);
}
}
}
- 当前线程数 < 核心线程数 创建核心线程执行任务
- 当前线程数 > 核心线程数 将任务添加到阻塞队列中
- 当阻塞队列中满了之后,创建救急线程,处理阻塞队列中的任务
- 当前线程数 > 最大线程数,则走拒绝策略
addWorker(Runnable var1, boolean var2)
用于想线程池中添加一个新的工作线程
var1 是 线程任务
var2 是 标志,true 则是要添加 核心线程 false则是要添加 非核心线程
private boolean addWorker(Runnable var1, boolean var2) {
while(true) {
// 检查线程池状态
int var3 = this.ctl.get();
int var4 = runStateOf(var3);
if (var4 >= 0 && (var4 != 0 || var1 != null || this.workQueue.isEmpty())) {
return false;
}
while(true) {
// 获得当前线程数
int var5 = workerCountOf(var3);
// var5 >= 536870911 是线程池中可容纳的最大的线程数量 2^29 个
// var2 是true 则是核心线程
// var2 是false则是救急线程
// 核心线程: 如果 当前线程数 < 核心线程数 则可以创建核心线程 否则返回false
// 救急线程: 如果 当前线程数 < 最大线程数 则可以创建救急线程 否则返回false
if (var5 >= 536870911 || var5 >= (var2 ? this.corePoolSize : this.maximumPoolSize)) {
return false;
}
// 尝试增加线程池中的线程数量
if (this.compareAndIncrementWorkerCount(var3)) {
boolean var18 = false;
boolean var19 = false;
ThreadPoolExecutor.Worker var20 = null;
try {
// 创建一个worker对象 封装任务和线程
var20 = new ThreadPoolExecutor.Worker(var1);
Thread var6 = var20.thread;
if (var6 != null) {
ReentrantLock var7 = this.mainLock;
var7.lock();
try {
int var8 = runStateOf(this.ctl.get());
//。。。。。 省略
// 将 worker添加到workers 这个hashset中
this.workers.add(var20);
//。。。。。 省略
} finally {
var7.unlock();
}
if (var19) {
// 启动线程 执行线程的run方法
var6.start();
var18 = true;
}
}
}
//。。。。。 省略
return var18;
}
//。。。。。 省略
}
}
}
- 判断想要增加的线程是 核心线程 还是 救急线程
- 如果是核心线程 判断 当前线程数 是否小于 核心线程数 小于则可以创建核心线程,否则则返回false
- 如果是非核心线程,判断 当前线程数 是否小于 最大线程数 小于则可以创建非核心线程(救急线程) 否则返回false
- 创建worker对象,封装任务runnable,并将worker添加到 workers的hashset集合中
- Worker.Thread.start() 启动线程执行run方法,就会去执行worker的run方法
worker实现了runnable接口,重写了run方法
Worker对象
Worker类 实现了Runnable接口,因此任务可以提交给线程池执行,重写run 方法,线程池执行任务会调用worker的run方法
在run方法中调用了runWorker() 这个方法
并继承了AQS会有一些并发的性质
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
private static final long serialVersionUID = 6138294804551838833L;
final Thread thread; // 线程
Runnable firstTask; // 任务
volatile long completedTasks; // 用于记录工作线程已完成的任务数量
Worker(Runnable var2) {
this.setState(-1);
this.firstTask = var2;
this.thread = ThreadPoolExecutor.this.getThreadFactory().newThread(this);
}
// run 方法
public void run() {
ThreadPoolExecutor.this.runWorker(this);
}
}
runWorker(ThreadPoolExecutor.Worker var1)
用于执行工作线程中的任务
final void runWorker(ThreadPoolExecutor.Worker var1) {
Thread var2 = Thread.currentThread();
Runnable var3 = var1.firstTask;
var1.firstTask = null;
var1.unlock();
boolean var4 = true;
try {
// 第一次进来 当前任务 var3(runnable) 不为空,是当前要执行的任务runnable,直接进入循环
// 第二次来, 去通过getTask() 在阻塞队列中去获取任务
while(var3 != null || (var3 = this.getTask()) != null) {
// 判断线程池状态
var1.lock();
if ((runStateAtLeast(this.ctl.get(), 536870912) || Thread.interrupted() && runStateAtLeast(this.ctl.get(), 536870912)) && !var2.isInterrupted()) {
var2.interrupt();
}
try {
// 执行线程之前的钩子函数
this.beforeExecute(var2, var3);
Object var5 = null;
try {
// 这里直接调用 这个Runnable 的run 方法 而不启动一个线程
var3.run();
}
// 。。。。省略
} finally {
// 执行线程后的钩子函数
this.afterExecute(var3, (Throwable)var5);
}
} finally {
// 每次执行完任务后 var3 runnable这个任务会置为空
var3 = null;
// 已完成线程数 +1 用来统计整个线程池中已经完成了的线程数
++var1.completedTasks;
var1.unlock();
}
}
var4 = false;
} finally {
this.processWorkerExit(var1, var4);
}
}
- 第一次执行,当前任务不为空,进入循环
- 检查线程池状态
- 执行线程之前的钩子函数this.beforeExecute(var2, var3)
- 调用runnable 任务的run 方法,执行任务
- 执行线程后的钩子函数 this.afterExecute(var3, (Throwable)var5);
getTask()
private Runnable getTask() {
boolean var1 = false;
while(true) {
int var2 = this.ctl.get();
int var3 = runStateOf(var2);
// 检查线程池状态
if (var3 >= 0 && (var3 >= 536870912 || this.workQueue.isEmpty())) {
this.decrementWorkerCount();
return null;
}
// 当前线程数
int var4 = workerCountOf(var2);
// allowCoreThreadTimeOut 如果为true 则核心线程 会被销毁
// 否则当前线程数大于核心线程数才会被销毁
// var5 为 true 则当前线程 可以被销毁 false 则不能被销毁
boolean var5 = this.allowCoreThreadTimeOut || var4 > this.corePoolSize;
if (var4 <= this.maximumPoolSize && (!var5 || !var1) || var4 <= 1 && !this.workQueue.isEmpty()) {
try {
// 从等待队列中拿到任务
// 如果当前线程可以被销毁 poll(this.keepAliveTime, TimeUnit.NANOSECONDS) 从任务队列中获取新任务,
// 并等待keepAliveTime时间,如果任务队列在等待时间内没有新任务,则返回null
//如果当前线程不可以被销毁 workQueue.take() 调用take去阻塞队列中拿任务,并一直等到新任务到来
Runnable var6 = var5 ? (Runnable)this.workQueue.poll(this.keepAliveTime, TimeUnit.NANOSECONDS) : (Runnable)this.workQueue.take();
if (var6 != null) {
return var6;
}
var1 = true;
} catch (InterruptedException var7) {
var1 = false;
}
} else if (this.compareAndDecrementWorkerCount(var2)) {
return null;
}
}
}
- 判断当前线程是否可以被销毁,调用allowCoreThreadTimeOut(true) 即可将核心线程数也设置为可销毁,
- 如果可以被销毁,调用 workQueue.poll(this.keepAliveTime, TimeUnit.NANOSECONDS)从任务队列中获取新任务,并等待keepAliveTime时间,如果任务队列在等待时间内没有新任务,则返回null
- 如果不可以被销毁,则调用workQueue.take()去阻塞队列中拿任务,并一直等到新任务到来
问题总结
什么是线程池
线程池是一种池化技术的实现,池化技术的核心思想其实就是实现资源的一个复用,避免资源的重复创建和销毁带来的性能开销。在线程池中,线程池可以管理一堆线程,让线程执行完任务之后不会进行销毁,而是继续去处理其它线程已经提交的任务。
使用线程池的好处
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
- 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统 的稳定性,使用线程池可以进行统一的分配,调优和监控
线程池的构造参数
- corePoolSize:线程池中用来工作的核心的线程数量。
- maximumPoolSize:最大线程数,线程池允许创建的最大线程数。
- keepAliveTime:超出 corePoolSize 后创建的线程存活时间或者是所有线程最大存活时间,取决于配置。
- unit:keepAliveTime 的时间单位。
- workQueue:任务队列,是一个阻塞队列,当线程数已达到核心线程数,会将任务存储在阻塞队列中。
- threadFactory :线程池内部创建线程所用的工厂。
- handler:拒绝策略;当队列已满并且线程数量达到最大线程数量时,会调用该方法处理该任务。
刚创建出来的线程池中有没有线程
刚创建出来的线程池中只有一个阻塞队列,此时没有任何线程。
如果想要在执行任务之前创建好核心线程,可以调用prestartAllCoreThreads() 方法来提前创建核心线程
并且在核心线程没有创建满之前,是一个一个去创建线程的。
线程池的运行原理
- 判断 当前线程数是否小于核心线程数,小于则创建核心线程来执行当前任务,当任务执行完之后,线程不会退出,而是会去从阻塞队列中获取任务
- 如果当前线程数 不小于 核心线程数,则会将 当前任务放入阻塞队列中,
- 如果阻塞队列已满,则会判断当前线程数是否小于最大线程数,满足则会去创建救急线程去执行任务,救急线程会存活一段时间,去从阻塞队列中获取任务,存活时间过后则会自行销毁
- 如果当前线程数 不小于 最大线程数 ,即无法创建新的线程了,则会走拒绝策略
拒绝策略
RejectedExecutionHandler的实现JDK自带的默认有4种
- AbortPolicy:丢弃任务,抛出运行时异常
- CallerRunsPolicy:由提交任务的线程来执行任务
- DiscardPolicy:丢弃这个任务,但是不抛异常
- DiscardOldestPolicy:从队列中剔除最先进入队列的任务,然后再次提交任务
线程池创建的时候,如果不指定拒绝策略就默认是AbortPolicy策略。当然,你也可以自己实现RejectedExecutionHandler接口,比如将任务存在数据库或者缓存中,这样就数据库或者缓存中获取到被拒绝掉的任务了。
线程池如何实现线程复用的
在runWorker 方法内部使用了while死循环,当第一个任务结束之后,会不断的通过getTask() 方法获取任务,只要能获取到任务,就会调用 当前任务的run 方法,继续执行这个任务
在执行run方法之前 有一个钩子函数beforeExecute() 可以重写这个方法来执行一些任务
在执行run方法之后 有一个钩子函数afterExecute() 可以重写这个方法来执行一些任务
核心线程会不会被销毁
默认情况下allowCoreThreadTimeOut是为false,核心线程即使在空闲的状态下也不会被销毁,会一直存活,直到线程关闭
当调用allowCoreThreadTimeOutt(true) 方法 后,核心线程则会和 救急线程一样在等待一定的存活时间后销毁
这样允许线程池在没有任务执行时减少资源消耗
核心线程和非核心线程的区别
他俩本质上没有区别,都是普通的线程 Thread
在线程池中,来新任务,如果需要创建线程,则会调用addWorker(runnable,boolean) 方法,如果创建核心线程,则是addWorker(runnable,true)
如果创建非核心线程则是addWorker(runnable,false)
然后通过worker对象将任务runnable封装,再创建一个线程,worker重写了run方法
线程是如何获取任务的,以及,如何实现超时销毁的
线程池通过调用getTask()方法,从阻塞队列中获取任务
- 判断当前线程是否可以被销毁 (allowCoreThreadTimeOut是否为true 或者 当前线程数大于核心线程数,则会被销毁)
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
- 如果是可以销毁的,则调用 workQueue.poll(this.keepAliveTime, TimeUnit.NANOSECONDS)方法,从阻塞队列里获取任务,并且等待keepAliveTime,如果没有获取到任务,就会被销毁
- 如果是不可以被消耗的,则调用workQueue.take()去阻塞队列中拿任务,并一直等到新任务到来 ,(拿不到任务一直阻塞)
线程池的五种状态
- RUNNING:线程池创建时就是这个状态,能够接收新任务,以及对已添加的任务进行处理。
- SHUTDOWN:调用shutdown方法线程池就会转换成SHUTDOWN状态,此时线程池不再接收新任务,但能继续处理已添加的任务到队列中任务。
- STOP:调用shutdownNow方法线程池就会转换成STOP状态,不接收新任务,也不能继续处理已添加的任务到队列中任务,并且会尝试中断正在处理的任务的线程。
- TIDYING:
SHUTDOWN 状态下,任务数为 0, 其他所有任务已终止,线程池会变为 TIDYING 状态。
线程池在 SHUTDOWN 状态,任务队列为空且执行中任务为空,线程池会变为 TIDYING 状态。
线程池在 STOP 状态,线程池中执行中任务为空时,线程池会变为 TIDYING 状态。 - TERMINATED:线程池彻底终止。线程池在 TIDYING 状态执行完 terminated() 方法就会转变为 TERMINATED 状态。
线程池状态具体是存在ctl成员变量中,ctl中不仅存储了线程池的状态还存储了当前线程池中线程数的大小
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
如何存储的
**int 一共32 为 用29为存储线程池的当前线程数,所以,线程池最多可以创建2^29 -1 个线程。其高3位用来存储 状态 **
如何优雅的关闭线程池
- 调用shutdown();会继续执行正在运行的任务和队列中等待的任务然后再关闭
- 等待了一段时间,调用awaitTermination(shutDownTimeOut, timeUnit)检查是否在时间内关闭
- 没有关闭则调用shutdownNow();进行强行关闭
pool.shutdown();
try {
if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
pool.shutdownNow();
}
} catch (InterruptedException e) {
pool.shutdownNow();
}
如何设置核心线程数
线程数设置的理论:
- CPU 密集型的程序 - 核心数 + 1
- I/O 密集型的程序 - 核心数 * 2理论公式在书中的定义《Java 并发编程实战》介绍了一个线程数计算的公式:Ncpu=CPU核心数Ucpu=目标CPU利用率,0<=Ucpu<=1w/c=等待时间和计算时间的比例如果希望程序跑到CPU的目标利用率,需要的线程数公式为:Nthreads= Ncpu*Ucpu(1+w/c)如果我期望目标利用率为90%(多核90),那么需要的线程数为:核心数12 * 利用率0.9 * (1 +50(sleep时间)/50(循环50_000_000耗时)) ≈ 22照上面的公式来规划线程数的话,误差一定会很大。因为此时这台主机上,已经有很多运行中的线程了,Tomcat有自己的线程池,HikariCP也有自己的后台线程,JVM也有一些编译的线程,连G1都有自己的后台线程。这些线程也是运行在当前进程、当前主机上的,也会占用CPU的资源。没有固定答案,先设定预期,比如我期望的CPU利用率在多少,负载在多少,GC频率多少之类的指标后,再通过测试不断的调整到一个合理的线程数流程一般是这样:
- 分析当前主机上,有没有其他进程干扰;分析当前JVM进程上,有没有其他运行中或可能运行的线
程
- 设定目标
目标CPU利用率 - 我最高能容忍我的CPU飙到多少?目标GC频率/暂停时间 - 多线程执行后,GC频
率会增高,最大能容忍到什么频率,每次暂停时间多少?
- 执行效率 - 比如批处理时,我单位时间内要开多少线程才能及时处理完毕
- ……根据预期,套用公式获取一个大概数值,再进行不断测试
- 梳理链路关键点,是否有卡脖子的点,因为如果线程数过多,链路上某些节点资源有限可能会导致
大量的线程在等待资源(比如三方接口限流,连接池数量有限,中间件压力过大无法支撑等)
- 不断的增加/减少线程数来测试,按最高的要求去测试,最终获得一个“满足要求”的线程数
很多的内部业务系统,并不需要啥性能,稳定好用符合需求就可以了。那么我的推荐的线程数是:CPU
核心数