线程池是什么
线程池就是创建若干个可执行的线程放入一个池(容器)中,有任务需要处理时,会提交到线程池中的任务队列,处理完之后线程并不会被销毁,而是仍然在线程池中等待下一个任务。
使用线程池的好处
- 降低资源消耗:通过重复利用现有的线程来执行任务,避免多次创建和销毁线程
- 提高响应速度:因为省去了创建线程这个步骤,所以在拿到任务时,可以立刻开始执行
- 提供附加功能:线程池的可拓展性使得我们可以自己加入新的功能,比如说定时、延时来执行某些线程
- 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配、调优和监控
线程池的类型
通过Executors工具来创建的
Executors 也是 java.util.concurrent 包下的成员,它是一个创建线程池的工厂,可以使用静态工厂方法来创建线程池
newFixedThreadPool
创建固定大小的线程池。每次提交一个任务就创建一个线程,知道线程达到线程池的最大大小。线程池的大小一旦达到最大值就保持不变,如果某一个线程因为执行异常结束,那么线程池会补充一个新线程。
newSingleThreadExecutor
创建一个单线程的线程池,这个线程池只有一个线程在工作,此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
newCachedThreadPool
创建一个可缓存的线程池,如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(1分钟不执行)的线程,当任务数增加是,此线程池又可以智能的添加新线程来处理任务,此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统能够创建线程的大小。
newScheduledThreadPool
创建一个大小无限的线程池,此线程池支持定时以及周期性的执行任务的需求。
自定义线程池
public class ThreadExecutorPoolTest {
static AtomicInteger atomicInteger = new AtomicInteger(0);
public static void main(String[] args) {
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
1,
3,
1,
TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(1),
(r -> new Thread(r,"t"+atomicInteger.incrementAndGet()))
);
}
}
线程池七大参数
corePoolSize
表示核心线程池的大小。当提交一个任务时,如果当前核心线程池的线程个数没有达到 corePoolSize,则会创建新的线程来执行所提交的任务,即使当前核心线程池有空闲的线程。如果当前核心线程池的线程个数已经达到了 corePoolSize,则不再重新创建线程。如果调用了prestartCoreThread()或者 prestartAllCoreThreads(),线程池创建的时候所有的核心线程都会被创建并且启动。
maximumPoolSize
线程池中最大线程数 , 如果线程池里的核心线程不够用了 , 此时阻塞队列满了 , 此时就会根据不同的线程池类型来增加线程 (非核心线程) , 但是最多把线程数量增加到 maximumPoolSize 指定的数量
keepAliveTime
非核心线程的存活时间 , 如果当前线程池的线程个数已经超过了 corePoolSize,并且线程空闲时间超过了 keepAliveTime 的话,就会将这些非核心线程销毁,这样可以尽可能降低系统资源消耗
unit
keepAliveTime的时间单位
workQueue
线程池的阻塞队列,用以保存任务 , 主要有 : ArrayBlockingQueue, LinkedBlockingQueue, SynchronousQueue, PriorityBlockingQueue
threadFactory
在线程池里创建线程的时候可以自己指定一个线程工厂,按照自己的方式创建线程出来 , 比如为每个创建出来的线程设置更有意义的名字,如果出现并发问题,也方便查找问题原因
handler
线程池的拒绝策略 , 如果线程池里的线程都在执行任务,然后等待队列满了,此时增加额外线程也达到了maximumPoolSize指定的数量,这个时候实在无法承载更多的任务了,此时就会执行不同的拒绝策略
-
AbortPolicy : 直接拒绝所提交的任务,并抛出RejectedExecutionException异常
-
CallerRunsPolicy : 只用调用者所在的线程来执行任务
-
DiscardPolicy : 不处理直接丢弃掉任务
-
DiscardOldestPolicy : 丢弃掉阻塞队列中存放时间最久的任务,执行当前任务
手写简单线程池
注意 : 这个手写可以理解是为了读源码前的一个准备工作 , 大概的知道线程池中每个组件,每个属性的作用是什么的 (放心,很简单,肯定可以看懂,并且写的时候可以大概的了解线程池的作者为什么要用特定的某个方法,不用其他可以平替的方法)
注意 : 下面的线程池中只有核心线程,没有非核心线程
准备工作
下面的代码大家可以直接拷贝到idea中来使用,使用前需要导入依赖和配置好日志文件 (类名我是用名字的首字母来的,大家也可以修改)
maven依赖 :
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.18</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.12</version>
</dependency>
</dependencies>
logback配置文件(放在resources目录下) :
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<!-- 日志存放路径 logs/job 设置为相对项目目录-->
<property name="log.path" value="logs/job" />
<!-- 日志输出格式 时间 线程 日志级别 类 方法 对应的行数 输出信息 这样设置后输出格式如下 -->
<!-- 15:09:27.204 [http-nio-8080-exec-10] DEBUG c.e.s.l.TestLog - [getVersion,26] - debug详细信息 -->
<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - %msg%n" />
<!-- 控制台输出 appender -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- 日志内容输出格式设置为定义好的 log.pattern-->
<pattern>${log.pattern}</pattern>
</encoder>
</appender>
<!-- 系统日志输出 appender class 中的log.pattern 表示日志滚动输出 -->
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 日志首次输出的文件地址 -->
<file>${log.path}/info.log</file>
<!-- 滚动输出策略:基于时间创建日志文件 ,这样第二天输出的日志,就会按照 fileNamePattern 新建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/info.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<!-- 日志内容输出格式设置为定义好的 log.pattern-->
<pattern>${log.pattern}</pattern>
</encoder>
<!-- 日志内容输出过滤器 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 过滤的级别 -->
<level>INFO</level>
<!-- 匹配时的操作:接收(记录) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配时的操作:拒绝(不记录) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<appender name="file_debug" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/debug.log</file>
<!-- 循环政策:基于时间创建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/debug.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 过滤的级别 -->
<level>DEBUG</level>
<!-- 匹配时的操作:接收(记录) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配时的操作:拒绝(不记录) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/error.log</file>
<!-- 循环政策:基于时间创建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/error.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 过滤的级别 -->
<level>ERROR</level>
<!-- 匹配时的操作:接收(记录) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配时的操作:拒绝(不记录) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 系统模块日志级别控制 name 设置为你自己的项目根路径 如com.example.logback-->
<!-- level 设置日志输出的级别为debug 这样系统在进行日志输出时 只要级别在 debug 之后都可以打印 -->
<!-- 日志输出级别 trace< debug < info< warn < error -->
<logger name="com.example.logback" level="debug" />
<!-- Spring日志级别控制-->
<logger name="org.springframework" level="warn" />
<!--系统操作日志 root 根路径的日志级别 info -->
<root level="info">
<!-- 将定义好的几个日志输出 追加到 root 上 -->
<!-- console 控制台输出 -->
<appender-ref ref="console" />
<!-- console info级别输出 -->
<appender-ref ref="file_info" />
<!-- console debug级输出 -->
<appender-ref ref="file_debug" />
<!-- console error级输出 -->
<appender-ref ref="file_error" />
</root>
</configuration>
线程池核心类
package com.changjunkai.ThreadPoolTest;
import lombok.extern.slf4j.Slf4j;
import java.util.HashSet;
import java.util.concurrent.TimeUnit;
/**
* @Author changjunkai
* @Date 2024/3/31 17:03
* @Description 线程池
*/
@Slf4j
public class CjkThreadPool {
private Object synLockObj = new Object();
/**
* 工作的线程
*/
private HashSet<CjkWorker> workers = new HashSet<CjkWorker>();
/**
* 阻塞队列
*/
private CjkBlockingQueue<Runnable> workQueue;
/**
* 核心线程数
*/
private int corePoolSize;
/**
* 超时等待获取任务时间
*/
private long timeOut;
/**
* 超时等待获取任务时间单位
*/
private TimeUnit timeUnit;
/**
* 拒绝策略
*/
private CjkRejectedHandler rejectedHandler;
public CjkThreadPool(int corePoolSize,
int queueCount,
long timeOut,
TimeUnit timeUnit,
CjkRejectedHandler rejectedHandler
) {
this.corePoolSize = corePoolSize;
this.workQueue = new CjkBlockingQueue<>(queueCount);
this.timeOut = timeOut;
this.timeUnit = timeUnit;
this.rejectedHandler = rejectedHandler;
}
public Object getSynLockObj() {
return synLockObj;
}
public void setSynLockObj(Object synLockObj) {
this.synLockObj = synLockObj;
}
public HashSet<CjkWorker> getWorkers() {
return workers;
}
public void setWorkers(HashSet<CjkWorker> workers) {
this.workers = workers;
}
public CjkBlockingQueue<Runnable> getWorkQueue() {
return workQueue;
}
public void setWorkQueue(CjkBlockingQueue<Runnable> workQueue) {
this.workQueue = workQueue;
}
public int getCorePoolSize() {
return corePoolSize;
}
public void setCorePoolSize(int corePoolSize) {
this.corePoolSize = corePoolSize;
}
public long getTimeOut() {
return timeOut;
}
public void setTimeOut(long timeOut) {
this.timeOut = timeOut;
}
public TimeUnit getTimeUnit() {
return timeUnit;
}
public void setTimeUnit(TimeUnit timeUnit) {
this.timeUnit = timeUnit;
}
public CjkRejectedHandler getRejectedHandler() {
return rejectedHandler;
}
public void setRejectedHandler(CjkRejectedHandler rejectedHandler) {
this.rejectedHandler = rejectedHandler;
}
public void execute(Runnable task) {
//存在并发问题,这里使用synchronized来加锁
synchronized (synLockObj) {
//1.线程池的大小小于核心线程数,此时证明还可以再创建线程
if (workers.size() < corePoolSize) {
CjkWorker cjkWorker = new CjkWorker(task);
log.info("{}-任务分配给了 --->{}",task,cjkWorker.getName());
workers.add(cjkWorker);
cjkWorker.start();
} else {
//带有拒绝策略的提交任务,具体策略由用户来设置
//一共有四种
//1.调用队列的put方法,这样的话就是如果队列满的话,阻塞等待其他线程执行完以后再插入队列,保证不丢弃任务
//2.调用队列可以设置超时时间的offer方法,这样不用傻傻的一直等待,如果到了时间还没有线程可以执行任务的话就丢弃了
//3.让调用者来执行当前任务
//4.直接丢弃任务,不做任务处理
workQueue.tryOffer(task,rejectedHandler);
}
}
}
/**
* 该类为线程池中线程封装对象,因为线程池中的线程不光执行完提交的一个任务就结束了,还要继续执行队列中源源不断来提交的任务
**/
class CjkWorker extends Thread {
//提交的任务,刚开始第一次肯定不为null,之后执行完以后就为null,此时需要从队列中获取任务来执行
private Runnable task;
public CjkWorker(Runnable task) {
this.task = task;
}
@Override
public void run() {
while(task != null || (task = workQueue.take(timeOut,timeUnit)) != null) {
task.run();
task = null;
}
}
}
/**
* 该类为task类,类内部只有一个name属性(任务名),继承了Runnable,主要是线程池中的线程来执行该任务,内部run方法只是打印日志
*/
static class CjkTask implements Runnable{
String name;
public CjkTask(String name) {
this.name = name;
}
@Override
public void run() {
try {
//模拟任务执行
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//打印执行的任务名称
log.info("执行:{}",name);
}
@Override
public String toString() {
return name;
}
}
}
阻塞队列实现
package com.changjunkai.ThreadPoolTest;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Author changjunkai
* @Date 2024/3/31 17:05
* @Description 阻塞队列
*/
@Slf4j
public class CjkBlockingQueue<T> {
/**
* 存放任务的队列
*/
private Deque<T> deque = new ArrayDeque<>();
/**
* 队列容量
**/
private int count;
/**
* 保证执行put和take方法的时候的线程安全
*/
private ReentrantLock rLock = new ReentrantLock();
/**
* take时为空的条件队列
*/
private Condition emptyCondition = rLock.newCondition();
/**
* put时容量满时的条件队列
*/
private Condition fullCondition = rLock.newCondition();
public CjkBlockingQueue() {
}
public CjkBlockingQueue(int count) {
this.count = count;
}
/**
* 从队列当中获取一个任务
* 线程阻塞获取元素
* take如果获取不到元素 队列size0---阻塞 , 解阻塞
*
* @return
*/
public T take() {
//加锁
rLock.lock();
try {
//判断队列是否为空
while (deque.isEmpty()) {
//为空则阻塞获取任务的线程 (等put一个元素的时候再唤醒)
log.info("[take] 队列为空,线程阻塞......");
emptyCondition.await();
}
//获取并删除掉一个元素
T t = deque.removeFirst();
log.info("获取:{}", t);
//因为已经拿到了一个元素,此时容量-1,这个时候put方法里可能有线程被await了,唤醒他们
fullCondition.signal();
return t;
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
//解锁
rLock.unlock();
}
}
public T take(long timeOut, TimeUnit timeUnit) {
long l = timeUnit.toNanos(timeOut);
//加锁
rLock.lock();
try {
//判断队列是否为空
while (deque.isEmpty()) {
if (l <= 0) {
log.info("[take] 队列为空,线程阻塞--超时时间 : {} -- 超时单位 : {},等待时间已到", timeOut, timeUnit);
return null;
}
//为空则阻塞获取任务的线程 (等put一个元素的时候再唤醒)
log.info("[take] 队列为空,线程阻塞--超时时间 : {} -- 超时单位 : {}......", timeOut, timeUnit);
//这里不用await(time,timeUnit)是因为 :
// 假如我传100毫秒,这时候等待了大概50毫秒,被外部打断了,这个醒过来发现还是空的队列,然后再睡,这个时候就会再睡 / 100毫秒,加起来就是睡了150毫秒
// 使用awaitNanos(time)可以返回剩余等待时间,更方便的控制等待后醒过来的情况
l = emptyCondition.awaitNanos(l);
}
//获取并删除掉一个元素
T t = deque.removeFirst();
log.info("获取:{}", t);
//因为已经拿到了一个元素,此时容量-1,这个时候put方法里可能有线程被await了,唤醒他们
fullCondition.signal();
return t;
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
//解锁
rLock.unlock();
}
}
/**
* 队列满的话,则一直阻塞,直到有线程执行完以后重新take,才能插入到队列中
* @param t
*/
public void put(T t) {
//加锁
rLock.lock();
try {
//判断队列已满
while (deque.size() == count) {
//已满则阻塞获取任务的线程 (等take掉一个元素的时候再唤醒)
log.info("[put] 队列已满,线程阻塞,{}......", t);
fullCondition.await();
}
log.info("{}:任务放到了队列中", t);
//添加一个元素
deque.addLast(t);
//添加一个元素后唤醒take时队列为空阻塞的线程
emptyCondition.signal();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
//解锁
rLock.unlock();
}
}
/**
* 指定超时时间来添加一个任务,到了指定时间队列还是满的话则丢弃任务
*
* @param t
* @param timeOut
* @param timeUnit
* @return
*/
public boolean offer(T t, long timeOut, TimeUnit timeUnit) {
long l = timeUnit.toNanos(timeOut);
//加锁
rLock.lock();
try {
//判断队列已满
while (deque.size() == count) {
if (l <= 0) {
log.info("[offer] 队列为空,任务 : {}阻塞时间已到,丢弃该任务",t);
return false;
}
//已满则阻塞获取任务的线程 (等take掉一个元素的时候再唤醒)
log.info("[offer] 队列为空,线程阻塞--超时时间 : {} -- 超时单位 : {} -- 任务 :{}......", timeOut, timeUnit,t);
l = fullCondition.awaitNanos(l);
}
log.info("{}:任务放到了队列中", t);
//添加一个元素
deque.addLast(t);
//添加一个元素后唤醒take时队列为空阻塞的线程
emptyCondition.signal();
return true;
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
//解锁
rLock.unlock();
}
}
/**
* 带指定拒绝策略的添加方法
*
* @param t
* @param rejectedHandler
* @return
*/
public void tryOffer(T t, CjkRejectedHandler rejectedHandler) {
//加锁
rLock.lock();
try {
//判断队列已满
if (deque.size() == count) {
//已满则阻塞获取任务的线程 (等take掉一个元素的时候再唤醒)
log.info("[put] 队列已满,执行拒绝策略");
rejectedHandler.reject((Runnable) t, this);
} else {
log.info("{}:任务放到了队列中", t);
//添加一个元素
deque.addLast(t);
//添加一个元素后唤醒take时队列为空阻塞的线程
emptyCondition.signal();
}
} finally {
//解锁
rLock.unlock();
}
}
}
拒绝策略接口
package com.changjunkai.ThreadPoolTest;
/**
* @Author changjunkai
* @Date 2024/3/31 23:09
* @Description
*/
public interface CjkRejectedHandler {
void reject(Runnable r, CjkBlockingQueue cjkBlockingQueue);
}
测试类
package com.changjunkai.ThreadPoolTest;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Author changjunkai
* @Date 2024/3/31 16:49
* @Description
*/
@Slf4j
public class PoolTest {
static ReentrantLock rLock = new ReentrantLock();
public static void main(String[] args) {
//构造参数含义 :
// corePoolSize : 核心线程数 (队列中常驻的线程数量)
// queueCount : 存储任务的队列长度
// timeOut :
CjkThreadPool cjkThreadPool = new CjkThreadPool(
1,
2,
10,
TimeUnit.SECONDS,
(task,queue) -> {
//这里有几种拒绝策略
//1.阻塞等待 (直接调用队列的put方法即可)
//queue.put(task);
//2.设置超时时间的阻塞等待
//queue.offer(task,1,TimeUnit.SECONDS);
//3.让调用者线程执行任务
//task.run();
//4.不做任务处理 (什么代码都不用写)
}
);
for (int i = 1; i <= 5; i++) {
//execute提交任务即可
cjkThreadPool.execute(new CjkThreadPool.CjkTask("task-" + i));
}
}
}
四种拒绝策略效果
假设这里核心线程数为1,队列容量为2,我们依次提交5个任务
这里提前说一下 阻塞等待和设置超时时间阻塞等待jdk的线程池中是没有这两个拒绝策略的,一共四个拒绝策略,只有AbortPolicy 和DiscardOldestPolicy 没有实现,各位可以学习了jdk的源码后改善自己写的线程池
阻塞等待
如果是阻塞等待的话,第1个线程正常会执行,因为线程池中没有线程,所以会创建1个线程直接就执行任务,第2个任务再次提交会放入阻塞队列中,第3个任务也是,第4个任务和第5个任务就会进入到wait等待队列中 (condition,这里需要了解下AQS),如果第一个线程成功执行完毕后,会重新从队列中take一个任务,此时线程会执行任务2,然后会唤醒一个等待的线程,这个时候,线程4就会苏醒,发现队列还有一个容量,此时就会添加到队列中来,第5个任务也是相同,任务2结束会重新从队列中take获取一个线程,此时第5个任务就也会被加入到队列中来
结果如下 :
//第一个任务提交的时候线程池中没有线程,所以会创建一个线程,将task1分配给thread0
01:02:13.306 [main] INFO c.c.T.CjkThreadPool - task-1-任务分配给了 --->Thread-0
//task2和3都会放入队列中,因为核心线程已经足够了
01:02:13.309 [main] INFO c.c.T.CjkBlockingQueue - task-2:任务放到了队列中
01:02:13.309 [main] INFO c.c.T.CjkBlockingQueue - task-3:任务放到了队列中
//task4来了,发现队列满了,此时再去插入会进行等待
01:02:13.309 [main] INFO c.c.T.CjkBlockingQueue - [put] 队列已满,执行拒绝策略
01:02:13.309 [main] INFO c.c.T.CjkBlockingQueue - [put] 队列已满,线程阻塞,task-4......
//这个时候task1执行好了
01:02:16.310 [Thread-0] INFO c.c.T.CjkThreadPool - 执行:task-1
//线程重新从队列中获取到了task2,开始执行
01:02:16.310 [Thread-0] INFO c.c.T.CjkBlockingQueue - 获取:task-2
//因为重新从队列中获取了一个元素,所以此时task4之前要插入的线程已经从等待中苏醒了,要将任务插入到队列中
01:02:16.310 [main] INFO c.c.T.CjkBlockingQueue - task-4:任务放到了队列中
//现在task5来了,发现task4插入后,队列还是满的,继续等待
01:02:16.310 [main] INFO c.c.T.CjkBlockingQueue - [put] 队列已满,执行拒绝策略
01:02:16.313 [main] INFO c.c.T.CjkBlockingQueue - [put] 队列已满,线程阻塞,task-5......
//task2执行完了,下面大家应该都能看懂了
01:02:19.319 [Thread-0] INFO c.c.T.CjkThreadPool - 执行:task-2
01:02:19.320 [Thread-0] INFO c.c.T.CjkBlockingQueue - 获取:task-3
01:02:19.320 [main] INFO c.c.T.CjkBlockingQueue - task-5:任务放到了队列中
01:02:22.332 [Thread-0] INFO c.c.T.CjkThreadPool - 执行:task-3
01:02:22.332 [Thread-0] INFO c.c.T.CjkBlockingQueue - 获取:task-4
01:02:25.344 [Thread-0] INFO c.c.T.CjkThreadPool - 执行:task-4
01:02:25.344 [Thread-0] INFO c.c.T.CjkBlockingQueue - 获取:task-5
01:02:28.349 [Thread-0] INFO c.c.T.CjkThreadPool - 执行:task-5
//这里模拟的是核心线程在指定时间内发现队列中没有元素的话,也就不存在了
01:02:28.349 [Thread-0] INFO c.c.T.CjkBlockingQueue - [take] 队列为空,线程阻塞--超时时间 : 10 -- 超时单位 : SECONDS......
01:02:38.351 [Thread-0] INFO c.c.T.CjkBlockingQueue - [take] 队列为空,线程阻塞--超时时间 : 10 -- 超时单位 : SECONDS,等待时间已到
设置超时时间的阻塞等待
因为上面每个task都会在内部睡眠3秒,所以我们设置的超时时间为1秒
//还是和上面一样,第一个任务来了,线程池中没有线程,所以会创建线程Thread0,然后分配task1给线程先执行
01:15:50.554 [main] INFO c.c.T.CjkThreadPool - task-1-任务分配给了 --->Thread-0
//将task2和3放入队列中,等待线程执行完手头上的任务后再执行
01:15:50.556 [main] INFO c.c.T.CjkBlockingQueue - task-2:任务放到了队列中
01:15:50.556 [main] INFO c.c.T.CjkBlockingQueue - task-3:任务放到了队列中
//这里是task4来了,开始执行拒绝策略,进行超时等待1秒,等了1秒后发现队列还不是空的,直接就丢弃任务了
01:15:50.556 [main] INFO c.c.T.CjkBlockingQueue - [put] 队列已满,执行拒绝策略
01:15:50.556 [main] INFO c.c.T.CjkBlockingQueue - [offer] 队列为空,线程阻塞--超时时间 : 1 -- 超时单位 : SECONDS -- 任务 :task-4......
01:15:51.571 [main] INFO c.c.T.CjkBlockingQueue - [offer] 队列为空,任务 : task-4阻塞时间已到,丢弃该任务\
//这里是task5,也是一样,等了1秒后发现队列还是满的,也丢弃了任务
01:15:51.571 [main] INFO c.c.T.CjkBlockingQueue - [put] 队列已满,执行拒绝策略
01:15:51.571 [main] INFO c.c.T.CjkBlockingQueue - [offer] 队列为空,线程阻塞--超时时间 : 1 -- 超时单位 : SECONDS -- 任务 :task-5......
01:15:52.581 [main] INFO c.c.T.CjkBlockingQueue - [offer] 队列为空,任务 : task-5阻塞时间已到,丢弃该任务
//这里task1执行完毕了
01:15:53.560 [Thread-0] INFO c.c.T.CjkThreadPool - 执行:task-1
//重新获取任务来执行
01:15:53.560 [Thread-0] INFO c.c.T.CjkBlockingQueue - 获取:task-2
01:15:56.571 [Thread-0] INFO c.c.T.CjkThreadPool - 执行:task-2
01:15:56.571 [Thread-0] INFO c.c.T.CjkBlockingQueue - 获取:task-3
01:15:59.581 [Thread-0] INFO c.c.T.CjkThreadPool - 执行:task-3
//我们发现task4和5确认没有被执行,丢弃了,因为到达了指定超时时间队列还不为空
01:15:59.581 [Thread-0] INFO c.c.T.CjkBlockingQueue - [take] 队列为空,线程阻塞--超时时间 : 10 -- 超时单位 : SECONDS......
01:16:09.584 [Thread-0] INFO c.c.T.CjkBlockingQueue - [take] 队列为空,线程阻塞--超时时间 : 10 -- 超时单位 : SECONDS,等待时间已到
调用者线程来执行任务
这里的调用者线程就是main线程
//第一个任务进来,创建线程,分配task1任务
01:23:32.501 [main] INFO c.c.T.CjkThreadPool - task-1-任务分配给了 --->Thread-0\
//线程2和3放入队列
01:23:32.505 [main] INFO c.c.T.CjkBlockingQueue - task-2:任务放到了队列中
01:23:32.505 [main] INFO c.c.T.CjkBlockingQueue - task-3:任务放到了队列中
//task4来了,发现队列满了,此时执行拒绝策略,但是是调用者线程来执行,也就是main线程
01:23:32.505 [main] INFO c.c.T.CjkBlockingQueue - [put] 队列已满,执行拒绝策略\
//我们发现3秒后(32 -> 35)任务执行完了,执行的线程就是main线程
01:23:35.506 [main] INFO c.c.T.CjkThreadPool - 执行:task-4
01:23:35.506 [Thread-0] INFO c.c.T.CjkThreadPool - 执行:task-1
01:23:35.506 [main] INFO c.c.T.CjkBlockingQueue - [put] 队列已满,执行拒绝策略
//task5也是同样
01:23:38.518 [main] INFO c.c.T.CjkThreadPool - 执行:task-5
01:23:38.518 [Thread-0] INFO c.c.T.CjkBlockingQueue - 获取:task-2
01:23:41.522 [Thread-0] INFO c.c.T.CjkThreadPool - 执行:task-2
01:23:41.523 [Thread-0] INFO c.c.T.CjkBlockingQueue - 获取:task-3
01:23:44.525 [Thread-0] INFO c.c.T.CjkThreadPool - 执行:task-3
01:23:44.525 [Thread-0] INFO c.c.T.CjkBlockingQueue - [take] 队列为空,线程阻塞--超时时间 : 10 -- 超时单位 : SECONDS......
01:23:54.538 [Thread-0] INFO c.c.T.CjkBlockingQueue - [take] 队列为空,线程阻塞--超时时间 : 10 -- 超时单位 : SECONDS,等待时间已到
不做任务处理(丢弃任务)
这个是最简单的,什么都不用写,我们只需要关注,task4和5有没有被执行就可以了,期望的是不执行
01:26:26.475 [main] INFO c.c.T.CjkThreadPool - task-1-任务分配给了 --->Thread-0
01:26:26.478 [main] INFO c.c.T.CjkBlockingQueue - task-2:任务放到了队列中
01:26:26.478 [main] INFO c.c.T.CjkBlockingQueue - task-3:任务放到了队列中
01:26:26.478 [main] INFO c.c.T.CjkBlockingQueue - [put] 队列已满,执行拒绝策略
01:26:26.478 [main] INFO c.c.T.CjkBlockingQueue - [put] 队列已满,执行拒绝策略
01:26:29.479 [Thread-0] INFO c.c.T.CjkThreadPool - 执行:task-1
01:26:29.479 [Thread-0] INFO c.c.T.CjkBlockingQueue - 获取:task-2
01:26:32.490 [Thread-0] INFO c.c.T.CjkThreadPool - 执行:task-2
01:26:32.490 [Thread-0] INFO c.c.T.CjkBlockingQueue - 获取:task-3
01:26:35.507 [Thread-0] INFO c.c.T.CjkThreadPool - 执行:task-3
01:26:35.507 [Thread-0] INFO c.c.T.CjkBlockingQueue - [take] 队列为空,线程阻塞--超时时间 : 10 -- 超时单位 : SECONDS......
01:26:45.508 [Thread-0] INFO c.c.T.CjkBlockingQueue - [take] 队列为空,线程阻塞--超时时间 : 10 -- 超时单位 : SECONDS,等待时间已到
发现,task4和5确实没有被执行
ThreadPoolExecutor线程池底层原理
接口关系
成员属性
public class ThreadPoolExecutor extends AbstractExecutorService {
//核心变量,32位的int型,主要分为两部分,高三位为线程池的状态,后29位为线程池内的线程数量
//workerCount限制为(2^29)-1(约5亿)线程
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3; //32 -3 = 29
private static final int CAPACITY = (1 << COUNT_BITS) - 1; //高3位000,低29位全为1
// runState is stored in the high-order bits
private static final int RUNNING = -1 << COUNT_BITS; //高3位111,低29位全为0
private static final int SHUTDOWN = 0 << COUNT_BITS; //高3位000,低29位全为0
private static final int STOP = 1 << COUNT_BITS; //高3位001,低29位全为0
private static final int TIDYING = 2 << COUNT_BITS; //高3位010,低29位全为0
private static final int TERMINATED = 3 << COUNT_BITS; //高3位011,低29位全为0
// Packing and unpacking ctl
//线程池的状态
private static int runStateOf(int c) { return c & ~CAPACITY; }
//线程池中工作线程的数量
private static int workerCountOf(int c) { return c & CAPACITY; }
//计算ctl的值,等于runState加上线程数量
private static int ctlOf(int rs, int wc) { return rs | wc; }
}
ctl属性
其主要包含两个信息 :
1 . 线程池的状态
2 . 线程池工作线程数量
线程池状态
- RUNNING:正常的运行状态,此时可以正常接收和处理任务。
- SHUTDOWN:关闭状态,只能通过调用shutdown()方法达到此状态。此时可以处理任务,但不再接受新任务,并且中断空闲的工作线程。
- STOP:停止状态,只能通过调用shutdownNow()方法达到此状态。此时清除队列中的未处理的任务,中断所有的(空闲的+正在执行任务的)工作线程。(不建议直接调用shutdownNow,会导致业务逻辑异常终止,会带来很多不可预知的问题)
- TIDYING:整理中状态,从SHUTDOWN和STOP自动流转到此状态。此时队列中任务为空,工作线程列表为空。
- TERMINATED:终止状态,从TIDYING状态自动流转到此状态。此时队列中任务为空,工作线程列表为空,并且已经执行完terminated回调函数。
构造参数
最少参数的构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
最多参数的构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
这里找了最少和最多参数的两个构造方法,可以看到针对,线程工厂和拒绝策略这两个参数是有默认值的
默认的线程工厂
static class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
由上述代码可知,默认线程工厂创建的线程名字为 : “pool-1-thread-” + num (从1开始递增)
创建一个线程池测试结果 :
02:23:13.385 [pool-1-thread-2] INFO c.c.T.CjkThreadPool - 执行:task4
02:23:13.385 [pool-1-thread-1] INFO c.c.T.CjkThreadPool - 执行:task0
02:23:14.391 [pool-1-thread-2] INFO c.c.T.CjkThreadPool - 执行:task1
02:23:14.391 [pool-1-thread-1] INFO c.c.T.CjkThreadPool - 执行:task2
02:23:15.392 [pool-1-thread-1] INFO c.c.T.CjkThreadPool - 执行:task3
默认的拒绝策略
private static final RejectedExecutionHandler defaultHandler =
new AbortPolicy();
核心线程和非核心线程的关系
我们都知道核心线程是基本上常驻在线程中的,并且是懒惰创建的,所谓懒惰创建,就是说不会在一开始构造函数就创建指定的线程数,而是到了真正提交任务的时候才去创建,而非核心线程的创建和核心线程达到了数量限制,并且队列容量也满了,这个时候会去创建 (maximumPoolSize - corePoolSize)个非核心线程,所以非核心线程其实也是懒惰创建
通过以下代码来验证 :
package com.changjunkai.ThreadPoolTest;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @Author changjunkai
* @Date 2024/4/1 1:37
* @Description
*/
@Slf4j
public class ThreadExecutorPoolTest {
static AtomicInteger atomicInteger = new AtomicInteger(0);
public static void main(String[] args) {
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
1,
2,
1,
TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(1),
(r -> new Thread(r,"t"+atomicInteger.incrementAndGet()))
);
for (int i = 1; i <= 3; i++) {
poolExecutor.execute(new CjkThreadPool.CjkTask("task" + i));
}
log.info("提交任务之后 线程数量 : {}",poolExecutor.getPoolSize());
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.info("提交任务之后(睡觉2秒后) 线程数量 : {}",poolExecutor.getPoolSize());
}
}
核心线程数量为 : 1
最大线程数量为 : 2
非核心线程存活时间为 :1毫秒
队列最大容量为 : 1
流程 : 提交第一个任务时创建一个核心线程 , 第二个任务进入了队列 , 第三个任务来了发现核心线程足够了,队列也满了,此时发现最大线程数为 2 ,还可以创建一个非核心线程来工作,所以提交任务后线程数量为 : 2,但是睡眠2秒后重新查看线程数量为 : 1, 此时非核心线程已经被回收了
(这里说一种极端情况,如果task3准备提交的时候t1正好执行完了,此时就不会再次创建非核心线程,所以创建非核心线程之前也必须确保核心线程是没有在空闲的)
结果如下 :
02:44:58.537 [main] INFO c.c.T.ThreadExecutorPoolTest - 提交任务之后 线程数量 : 2
02:44:59.548 [t1] INFO c.c.T.CjkThreadPool - 执行:task1
02:44:59.548 [t2] INFO c.c.T.CjkThreadPool - 执行:task3
02:45:00.544 [main] INFO c.c.T.ThreadExecutorPoolTest - 提交任务之后(睡觉2秒后) 线程数量 : 1
02:45:00.559 [t1] INFO c.c.T.CjkThreadPool - 执行:task2
根据这个例子我们再想一下,核心线程和非核心线程如果同时都空闲,此时队列中有一个元素,那么谁会拿到任务的执行权 ?
答案是随机的,上面这个例子中task2就是核心线程和非核心线程 "抢夺"的任务,根据结果我们发现是随机的
t1获取
02:51:03.359 [t1] INFO c.c.T.CjkThreadPool - 执行:task1
02:51:03.359 [t2] INFO c.c.T.CjkThreadPool - 执行:task3
02:51:04.370 [t1] INFO c.c.T.CjkThreadPool - 执行:task2
t2获取
02:51:27.411 [t1] INFO c.c.T.CjkThreadPool - 执行:task1
02:51:27.411 [t2] INFO c.c.T.CjkThreadPool - 执行:task3
02:51:28.425 [t2] INFO c.c.T.CjkThreadPool - 执行:task2
问题一 : 当实例化一个核心线程数为0 , 队列为无界队列的线程池为什么会启用非核心线程
问题二 : 为什么核心线程不会销毁 , 而非核心线程会销毁的原因 ?
问题三 : execute提交任务 , 执行任务 , 保存任务到队列的整个流程
execute方法
提交任务
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
//获取ctl
int c = ctl.get();
//如果线程池中线程数量 小于 核心线程数就创建线程执行任务
if (workerCountOf(c) < corePoolSize) {
//增加核心线程,command就是任务,成功直接返回
if (addWorker(command, true))
return;
//线程添加不成功,需要再次判断,每需要一次判断都会获取 ctl 的值
c = ctl.get();
}
//执行到这里有两种情况
// 1.添加核心线程失败
// 2.工作线程大于核心线程数
//线程池正在运行并且核心线程池已满就会把任务放到阻塞队列中来
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);
}
//能够执行到这里有几种情况
// 1.线程池不是运行状态
// 2.阻塞队列也满了
//此时尝试添加非核心线程来执行任务
//如果添加非核心线程失败的话那有两种情况
// 1.超过了线程池最大容量 (2^29)
// 2.工作线程数量超过了maximumPoolSize
else if (!addWorker(command, false))
//能执行到这里,说明
// 1.核心线程满了
// 2.阻塞队列也满了
// 3.非核心线程也没法创建了,超过了maximumPoolSize
// 没办法了只能执行拒绝策略
//执行拒绝策略
reject(command);
}
注意 :
‘’ 由于不知名原因,线程池中没有了线程,此时添加一个无任务的工作线程 "
这个可以想一下有什么场景是这样的 , 我能进入到这里说明我核心线程满了,但是线程中却没有了线程 ? 有一种场景是我核心线程设置的就是 0 ! , 但我队列是无界的,如果没有这个条件的话,那任务一个都执行不了了,都在阻塞队列里了
看了上面的代码以后我们可以知道 : 什么时候才会执行非核心线程? 的条件就是 :
- 线程池为
RUNNING
状态; - 核心线程数量已满;
- 添加到工作队列
workQueue
失败; - 工作线程数量小于线程最大容量
2^29-1
和maximumPoolSize
成员属性
addWorker
添加工作线程
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
//本段逻辑 :
// 1.判断线程池状态是否满足添加工作线程
// 2.判断线程池容量是否可以添加和使用cas增加工作线程数量
for (;;) {
//获取ctl
int c = ctl.get();
//获取线程池状态
int rs = runStateOf(c);
// Check if queue empty only if necessary.
//如果 rs >= SHUTDOWN说明,线程池状态可能为SHUTDOWN、STOP、TIDYING、TERMINATED
//不管是哪个状态,线程池其实都是拒绝再接收新任务的,这个条件是一个前提
// rs == SHUTDOWN : 表示线程池是调用了shutdown方法,还没有到终止状态
// firstTask == null : 没有提交任务,只提交了工作线程
// ! workQueue.isEmpty() : 队列不为空表示还有任务没有消费完
//同时满足上述三个条件才能继续往下新增工作线程
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
//这个for循环是尝试使用cas将ctl中的线程数量 + 1
for (;;) {
int wc = workerCountOf(c);
//如果大于了CAPACITY(2^29 -1) , 很少有计算机能达到这个值,大于就返回false
//如果core为true : 说明调用方想创建核心线程,此时看下核心线程容量是否足够 , 大于了就返回false
//如果core为false : 说明调用方想创建非核心线程 , 此时看下设置的最大线程数,如果大于了就返回false
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//走到这里,尝试通过cas将ctl中线程数量 + 1
if (compareAndIncrementWorkerCount(c))
//cas成功则跳出循环
break retry;
//走到这里说明cas失败了 (可能有其他线程并发的修改了线程池状态或者工作线程数量),重新获取ctl的值
c = ctl.get(); // Re-read ctl
//重新获取线程池状态,如果和之前的不一样的话则跳转到retry标记处重新获取线程池状态
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
//如果不是因为线程池状态问题导致的工作线程数变更失败,那么就继续执行内部循环逻辑(自旋重试)
//(给大家解释一下这里,如果能走到这里,说明工作线程的状态没有变,此时就不用跳转到retry标记处重新获取状态,说明 cas失败是因为其他线程更改了工作线程数量,继续在本循环中自旋)
}
}
//工作线程是否启动
boolean workerStarted = false;
//工作线程是否成功添加
boolean workerAdded = false;
Worker w = null; //工作线程对象
try {
//创建一个工作线程 (内部还会创建一个thread)
w = new Worker(firstTask);
//获取内部创建的线程
final Thread t = w.thread;
//这里我们思考一下,什么时候内部创建的线程是空的 .
//在执行worker的构造函数时,就会通过线程工厂来创建一个线程,而工厂是一个接口,用户可以自定义实现类,用户实现的new Thread可能失误返回null
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)) {
//看下创建出的线程是否已经执行了start方法,如果已经执行了说明有问题,抛出异常
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
//将worker对象放入工作线程集合中
workers.add(w);
//记录工作线程数量最大值
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true; //添加成功标识改为true
}
} finally {
//解锁
mainLock.unlock();
}
//成功添加
if (workerAdded) {
//启动线程,执行内部的run方法 -> runTask
t.start();
workerStarted = true;
}
}
} finally {
//如果启动失败做一些回滚处理
// 将启动失败的线程从工作线程列表中移除
// 工作线程数量减一
if (! workerStarted)
addWorkerFailed(w);
}
//返回是否成功启动
return workerStarted;
}
runWorker (Worker内部run方法里调用的)
final void runWorker(Worker w) {
//获取当前线程
Thread wt = Thread.currentThread();
//获取需要首先执行的任务赋值给task变量
Runnable task = w.firstTask;
//将worker内部的firstTask属性置为null
w.firstTask = null;
//这里不是解锁,而是将state置为0,并且将ExclusiveOwnerThread置为null,相当于做了初始化,因为一开始state为-1,不允许抢 //占
w.unlock(); // allow interrupts
//表示是否正常退出
// true : 当前线程发生异常了突然退出,非正常while循环不满足退出
// false : 正常退出
boolean completedAbruptly = true;
try {
//task != null , 如果是核心线程 或者 非核心线程第一次来,task肯定不为空,先执行带来的任务
//(task = getTask()) != null , 说明已经执行过了一次任务,此时task为空,现在需要从队列中获取任务来执行
// getTask()内部会有判断,如果设置了allowCoreThreadTimeOut为true,表示核心线程也会和非核心线程一样,空 // 闲时间到了就销毁,如果为false,则说明线程池中核心线程不会销毁,只会获取队列任务中阻塞,而非核心线程就会在达 // 到空闲时间后销毁
while (task != null || (task = getTask()) != null) {
//加锁,防止其他线程将当前线程中断
// 为什么要设置独占锁?shutdown时会判断当前worker状态,根据独占锁是否空闲来判断当前worker是否正在工作
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
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();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
getTask
到当前的任务队列获取任务,如果能获取到就返回,如果获取不到则会阻塞当前线程,当达到某种条件时会返回 null。可能会超时也有可能不会,根据当前线程是否可以被回收决定
private Runnable getTask() {
表示当前线程获取任务是否超时,默认是false,true表示已超时
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
//获取ctl的值
int c = ctl.get();
//线程池状态
int rs = runStateOf(c);
// Check if queue empty only if necessary.
//条件1 :rs >= SHUTDOWN : 表示当前线程是非running状态,可能是SHUTDOWN或STOP
//条件2 : (rs >= STOP || workQueue.isEmpty())
// rs >= stop : 满足则说明当前线程状态最少也是STOP,条件1肯定也满足,此时直接返回null
// workQueue.isEmpty() : 满足则说明当前线程条件1满足,并且条件2中的rs >= stop不满足,说明当前线程状态为 // SHUTDOWN,此时队列如果为空,则返回null
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
//工作线程数量-1
decrementWorkerCount();
return null;
}
//如果能执行到这里有几种情况
// 1.线程池状态为running
// 2.线程池状态为
//获取工作线程数量SHUTDOWN , 但是队列中还有任务
int wc = workerCountOf(c);
// Are workers subject to culling?
//工作线程是否会被淘汰
// 如果设置了allowCoreThreadTimeOut为true,则线程池中的线程不管核心还是非核心都会空闲后销毁,使用 // workQueue.poll(xx,xx)
// 如果allowCoreThreadTimeOut为false,则线程池中会保留有足够的核心线程数量的线程,使用workQueue.take();
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
//条件1 :(wc > maximumPoolSize || (timed && timedOut))
// wc > maximumPoolSize : 为什么能成立,感觉不会成立呀,因为addWorker方法中有判断,不可能会大于呀 . 可能有外 // 部线程调用了setMaximumPoolSize()方法,导致比原来构造的线程最大值小了
// timed && timedOut : 线程可以超时等待并且之前已经超时获取过了
//条件1如果为true,表示线程可以被回收,但可能不是现在,需要看条件2
//条件2 : (wc > 1 || workQueue.isEmpty())
// wc > 1 : 当前工作线程数量大于1,说明当前线程可以直接回收
// workQueue.isEmpty() : 前置条件wc <= 1,此时线程池中只有当前线程了,并且队列也空了,是最后一个线程,也可以走 // 了
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
//timed如果为true则表示当前当前线程会带超时时间的去队列中获取任务,到了时间还没有则线程退出(可能是核心线程,也 // 可能是非核心线程)
//timed如果为false则表示当前为核心线程(此时线程池中的工作线程数量一定小于等于corePoolSize),阻塞获取任务即 // 可,不用超时等待,该线程也不会被销毁
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
//获取到了任务不为null就返回
//如果是超时等待,可能会为空也可能不为空
if (r != null)
return r;
//走到这里说明当前线程超时等待还没有任务,继续自旋
//设置timeOut为true
timedOut = true;
} catch (InterruptedException retry) {
//被打断设置为false
timedOut = false;
}
}
}