一、线程池的概念
初识线程池
- 我们知道,线程的创建和销毁都需要映射到操作系统,因此其代价是比较高昂的。出于避免频繁创建、销毁线程以及方便线程管理的需要,线程池应运而生。
- 线程池是一组预先创建好的线程,它们处于等待状态,随时准备执行任务。当有新的任务提交时,线程池中的线程会被分配去执行任务,任务完成后,线程会回到线程池中等待下一个任务。
二、使用线程池的好处
线程池优势
-
降低资源消耗:线程池通常会维护一些线程(数量为 corePoolSize),这些线程被重复使用来执行不同的任务,减少了线程创建和销毁所带来的系统开销,任务完成后不会销毁。在待处理任务量很大的时候,通过对线程资源的复用,避免了频繁地创建和销毁线程对系统资源的浪费。
-
提高响应速度:任务可以直接提交给线程池,无需等待线程的创建,而是直接由这些线程去执行任务,从而减少了任务的等待时间。
-
提高线程的可管理性:线程池可以统一管理线程,包括线程的创建、启动、停止等操作,方便进行线程的监控和调优。
线程池设计思路
有句话叫做艺术来源于生活,编程语言也是如此,很多设计思想能映射到日常生活中,比如面向对象思想、封装、继承,等等。今天我们要说的线程池,它同样可以在现实世界找到对应的实体——线程工厂(ThreadFactory)。
先假想一个工厂的生产流程:
工厂中有固定的一批工人,称为正式工人,工厂接收的订单由这些工人去完成。当订单增加,正式工人已经忙不过来了,工厂会将生产原料暂时堆积在仓库中,等有空闲的工人时再处理(因为工人空闲了也不会主动处理仓库中的生产任务,所以需要调度员实时调度)。仓库堆积满了后,订单还在增加怎么办?
工厂只能临时扩招一批工人来应对生产高峰,而这批工人高峰结束后是要清退的,所以称为临时工。当时临时工也以招满后(受限于工位限制,临时工数量有上限),后面的订单只能忍痛拒绝了。
我们做如下一番映射:
-
工厂——线程池
-
订单——任务(Runnable)
-
正式工人——核心线程
-
临时工——普通线程
-
仓库——任务队列
-
调度员——getTask()
getTask()是一个方法,将任务队列中的任务调度给空闲线程,在解读线程池有详细介绍
映射后,形成线程池流程图如下,两者是不是有异曲同工之妙?这样,线程池的工作原理或者说流程就很好理解了,提炼成一个简图:
深入线程池
那么接下来,问题来了,线程池是具体如何实现这套工作机制的呢?
从Java线程池Executor框架体系可以看出:线程池的真正实现类是ThreadPoolExecutor,因此我们接下来重点研究这个类。
三、线程池的核心参数
构造方法
研究一个类,先从它的构造方法开始。ThreadPoolExecutor提供了4个有参构造方法:
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) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
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.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
解释一下构造方法中涉及到的参数:
-
corePoolSize(必需): 核心线程数。
-
即池中一直保持存活的线程数,即使这些线程处于空闲。
-
即使这些线程处于空闲状态,它们也不会被销毁,除非设置了allowCoreThreadTimeOut为true
-
-
maximumPoolSize(最大线程数(必需)):
-
池中允许的最大线程数。
-
当任务队列已满且正在运行的线程数小于
maximumPoolSize
时,会创建新的线程来执行任务。
-
-
keepAliveTime(线程存活时间(必需)):
-
当线程池中的线程数量超过
corePoolSize
时,多余的空闲线程在等待新任务到来时的最大存活时间。 -
当非核心线程处于空闲状态的时间超过这个时间后,该线程将被回收。将allowCoreThreadTimeOut参数设置为true后,核心线程也会被回收。
-
-
unit(时间单位(必需)):
-
用于指定
keepAliveTime
的时间单位,如秒、毫秒等。 -
有:
TimeUnit.DAYS
(天)、TimeUnit.HOURS
(小时)、TimeUnit.MINUTES
(分钟)、TimeUnit.SECONDS
(秒)、TimeUnit.MILLISECONDS
(毫秒)、TimeUnit.MICROSECONDS
(微秒)、TimeUnit.NANOSECONDS
(纳秒)
-
-
workQueue(任务队列(必需)):
-
任务队列,采用阻塞队列实现,用于存储等待执行的任务。
-
当核心线程全部繁忙时,后续由execute方法提交的Runnable将存放在任务队列中,等待被线程处理,常见的任务队列有
LinkedBlockingQueue
、ArrayBlockingQueue
等。
-
-
threadFactory(线程工厂(可选)):
-
线程工厂。指定线程池创建线程的方式。
-
用于创建线程的工厂,可以设置线程的名称、优先级等属性
-
-
handler(拒绝策略(可选)):
-
当线程池中线程数达到maximumPoolSize且workQueue打满时,后续提交的任务将被拒绝,handler可以指定用什么方式拒绝任务。
-
常见的拒绝策略有
AbortPolicy
(直接抛出异常)、CallerRunsPolicy
(由提交任务的线程执行任务)、DiscardPolicy
(丢弃任务)、DiscardOldestPolicy
(丢弃最老的任务)。
-
放到一起再看一下:
任务队列
使用ThreadPoolExecutor需要指定一个实现了BlockingQueue接口的任务等待队列。在ThreadPoolExecutor线程池的API文档中,一共推荐了三种等待队列,它们是:SynchronousQueue、LinkedBlockingQueue和ArrayBlockingQueue;
-
SynchronousQueue: 同步队列。这是一个内部没有任何容量的阻塞队列,任何一次插入操作的元素都要等待相对的删除/读取操作,否则进行插入操作的线程就要一直等待,反之亦然。
-
LinkedBlockingQueue: 无界队列(严格来说并非无界,上限是
Integer.MAX_VALUE
),基于链表结构。使用无界队列后,当核心线程都繁忙时,后续任务可以无限加入队列,因此线程池中线程数不会超过核心线程数。这种队列可以提高线程池吞吐量,但代价是牺牲内存空间,甚至会导致内存溢出。另外,使用它时可以指定容量,这样它也就是一种有界队列了。 -
ArrayBlockingQueue: 有界队列,基于数组实现。在线程池初始化时,指定队列的容量,后续无法再调整。这种有界队列有利于防止资源耗尽,但可能更难调整和控制。
另外,Java还提供了另外4种队列:
-
PriorityBlockingQueue: 支持优先级排序的无界阻塞队列。存放在PriorityBlockingQueue中的元素必须实现Comparable接口,这样才能通过实现
compareTo()
方法进行排序。优先级最高的元素将始终排在队列的头部;PriorityBlockingQueue不会保证优先级一样的元素的排序,也不保证当前队列中除了优先级最高的元素以外的元素,随时处于正确排序的位置。 -
DelayQueue: 延迟队列。基于二叉堆实现,同时具备:无界队列、阻塞队列、优先队列的特征。DelayQueue延迟队列中存放的对象,必须是实现Delayed接口的类对象。通过执行时延从队列中提取任务,时间没到任务取不出来。更多内容请见DelayQueue。
-
LinkedBlockingDeque: 双端队列。基于链表实现,既可以从尾部插入/取出元素,还可以从头部插入元素/取出元素。
-
LinkedTransferQueue: 由链表结构组成的无界阻塞队列。这个队列比较特别的时,采用一种预占模式,意思就是消费者线程取元素时,如果队列不为空,则直接取走数据,若队列为空,那就生成一个节点(节点元素为null)入队,然后消费者线程被等待在这个节点上,后面生产者线程入队时发现有一个元素为null的节点,生产者线程就不入队了,直接就将元素填充到该节点,并唤醒该节点等待的线程,被唤醒的消费者线程取走元素。
拒绝策略
线程池有一个重要的机制:拒绝策略。当线程池workQueue已满且无法再创建新线程池时,就要拒绝后续任务了。拒绝策略需要实现RejectedExecutionHandler
接口,不过Executors框架已经为我们实现了4种拒绝策略:
-
AbortPolicy(默认): 丢弃任务并抛出RejectedExecutionException异常。
-
CallerRunsPolicy: 直接运行这个任务的run方法,但并非是由线程池的线程处理,而是交由任务的调用线程处理。
-
DiscardPolicy: 直接丢弃任务,不抛出任何异常。
-
DiscardOldestPolicy: 将当前处于等待队列列头的等待任务强行取出,然后再试图将当前被拒绝的任务提交到线程池执行。
线程工厂指定创建线程的方式,这个参数不是必选项,Executors类已经为我们非常贴心地提供了一个默认的线程工厂:
/**
* yun
*/
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;
}
}
线程池状态
线程池有5种状态:
volatile int runState;
// 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;
runState表示当前线程池的状态,它是一个 volatile 变量用来保证线程之间的可见性。
下面的几个static final变量表示runState可能的几个取值,有以下几个状态:
-
RUNNING: 当创建线程池后,初始时,线程池处于RUNNING状态;
-
SHUTDOWN: 如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;
-
STOP: 如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;
-
TERMINATED: 当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。
初始化&容量调整&关闭
1、线程初始化
默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程。
在实际中如果需要线程池创建之后立即创建线程,可以通过以下两个方法办到:
-
prestartCoreThread():
boolean prestartCoreThread()
,初始化一个核心线程 -
prestartAllCoreThreads():
int prestartAllCoreThreads()
,初始化所有核心线程,并返回初始化的线程数
public boolean prestartCoreThread() {
return addIfUnderCorePoolSize(null); //注意传进去的参数是null
}
public int prestartAllCoreThreads() {
int n = 0;
while (addIfUnderCorePoolSize(null))//注意传进去的参数是null
++n;
return n;
}
2、线程池关闭
ThreadPoolExecutor提供了两个方法,用于线程池的关闭:
-
shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
-
shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
3、线程池容量调整
ThreadPoolExecutor提供了动态调整线程池容量大小的方法:
-
setCorePoolSize:设置核心池大小
-
setMaximumPoolSize:设置线程池最大能创建的线程数目大小
当上述参数从小变大时,ThreadPoolExecutor
进行线程赋值,还可能立即创建新的线程来执行任务。
四、使用线程池
ThreadPoolExecutor
通过构造方法使用ThreadPoolExecutor
是线程池最直接的使用方式,下面看一个实例:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class MyTest {
public static void main(String[] args) {
// 创建线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(3, 5, 5, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(5));
// 向线程池提交任务
for (int i = 0; i < threadPool.getCorePoolSize(); i++) {
threadPool.execute(new Runnable() {
@Override
public void run() {
for (int x = 0; x < 2; x++) {
System.out.println(Thread.currentThread().getName() + ":" + x);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
}
// 关闭线程池
threadPool.shutdown(); // 设置线程池的状态为SHUTDOWN,然后中断所有没有正在执行任务的线程
// threadPool.shutdownNow(); // 设置线程池的状态为STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表,该方法要慎用,容易造成不可控的后果
}
}
运行结果:
pool-1-thread-2:0
pool-1-thread-1:0
pool-1-thread-3:0
pool-1-thread-2:1
pool-1-thread-3:1
pool-1-thread-1:1
Executors封装线程池
另外,Executors封装好了4种常见的功能线程池(还是那么地贴心):
1、FixedThreadPool
固定容量线程池。其特点是最大线程数就是核心线程数,意味着线程池只能创建核心线程,keepAliveTime为0,即线程执行完任务立即回收。任务队列未指定容量,代表使用默认值Integer.MAX_VALUE
。适用于需要控制并发线程的场景。
// 使用默认线程工厂
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
// 需要自定义线程工厂
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
使用示例:
// 1. 创建线程池对象,设置核心线程和最大线程数为5
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
// 2. 创建Runnable(任务)
Runnable task =new Runnable(){
public void run() {
System.out.println(Thread.currentThread().getName() + "--->运行");
}
};
// 3. 向线程池提交任务
fixedThreadPool.execute(task);
2、 SingleThreadExecutor
单线程线程池。特点是线程池中只有一个线程(核心线程),线程执行完任务立即回收,使用有界阻塞队列(容量未指定,使用默认值Integer.MAX_VALUE
)
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
// 为节省篇幅,省略了自定义线程工厂方式的源码
使用示例:
// 1. 创建单线程线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
// 2. 创建Runnable(任务)
Runnable task = new Runnable(){
public void run() {
System.out.println(Thread.currentThread().getName() + "--->运行");
}
};
// 3. 向线程池提交任务
singleThreadExecutor.execute(task);
3、 ScheduledThreadPool
定时线程池。指定核心线程数量,普通线程数量无限,线程执行完任务立即回收,任务队列为延时阻塞队列。这是一个比较特别的线程池,适用于执行定时或周期性的任务。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
// 继承了 ThreadPoolExecutor
public class ScheduledThreadPoolExecutor extends ThreadPoolExecutor
implements ScheduledExecutorService {
// 构造函数,省略了自定义线程工厂的构造函数
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
// 延时执行任务
public ScheduledFuture<?> schedule(Runnable command,
long delay,
TimeUnit unit) {
...
}
// 定时执行任务
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit) {...}
}
使用示例:
// 1. 创建定时线程池
ExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
// 2. 创建Runnable(任务)
Runnable task = new Runnable(){
public void run() {
System.out.println(Thread.currentThread().getName() + "--->运行");
}
};
// 3. 向线程池提交任务
scheduledThreadPool.schedule(task, 2, TimeUnit.SECONDS); // 延迟2s后执行任务
scheduledThreadPool.scheduleAtFixedRate(task,50,2000,TimeUnit.MILLISECONDS);// 延迟50ms后、每隔2000ms执行任务
4、CachedThreadPool
缓存线程池。没有核心线程,普通线程数量为Integer.MAX_VALUE
(可以理解为无限),线程闲置60s后回收,任务队列使用SynchronousQueue
这种无容量的同步队列。适用于任务量大但耗时低的场景。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
使用示例:
// 1. 创建缓存线程池
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
// 2. 创建Runnable(任务)
Runnable task = new Runnable(){
public void run() {
System.out.println(Thread.currentThread().getName() + "--->运行");
}
};
// 3. 向线程池提交任务
cachedThreadPool.execute(task);
解读线程池
OK,相信前面内容阅读起来还算轻松愉悦吧,那么从这里开始就进入深水区了,如果后面内容能吃透,那么线程池知识就真的被你掌握了。
我们知道,向线程池提交任务是用ThreadPoolExecutor
的execute()
方法,但在其内部,线程任务的处理其实是相当复杂的,涉及到ThreadPoolExecutor
、Worker
、Thread
三个类的6个方法:
execute()
在ThreadPoolExecutor
类中,任务提交方法的入口是execute(Runnable command)
方法(submit()
方法也是调用了execute()
),该方法其实只在尝试做一件事:经过各种校验之后,调用 addWorker(Runnable command,boolean core)
方法为线程池创建一个线程并执行任务,与之相对应,execute()
的结果有两个:
参数说明:
-
Runnable command:
-
一个实现了
Runnable
接口的任务对象。这个任务将被提交到线程池中等待执行 -
可以是一个独立的
Runnable
实现类的实例,也可以是使用 Lambda 表达式创建的匿名Runnable
对象。
-
功能描述
- 任务提交
- 当调用
execute()
方法时,线程池会尝试将任务添加到内部的任务队列中,或者如果有空闲线程,则直接分配一个线程来执行这个任务。
- 当调用
- 线程分配
- 如果线程池中当前的线程数量小于核心线程数,线程池会创建一个新的线程来执行任务。
- 如果线程数量已经达到核心线程数,新提交的任务会被放入任务队列等待执行。
- 当任务队列已满且线程数量小于最大线程数时,线程池会创建新的线程来执行任务。
- 如果任务队列已满且线程数量已经达到最大线程数,线程池会根据拒绝策略来处理无法提交的任务。
执行流程:
1、通过 ctl.get()
得到线程池的当前线程数,如果线程数小于corePoolSize,则调用 addWorker(commond,true)
方法创建新的线程执行任务,否则执行步骤2;
2、步骤1失败,说明已经无法再创建新线程,那么考虑将任务放入阻塞队列,等待执行完任务的线程来处理。基于此,判断线程池是否处于Running状态(只有Running状态的线程池可以接受新任务),如果任务添加到任务队列成功则进入步骤3,失败则进入步骤4;
3、来到这一步需要说明任务已经加入任务队列,这时要二次校验线程池的状态,会有以下情形:
-
线程池不再是Running状态了,需要将任务从任务队列中移除,如果移除成功则拒绝本次任务
-
线程池是Running状态,则判断线程池工作线程是否为0,是则调用
addWorker(commond,true)
添加一个没有初始任务的线程(这个线程将去获取已经加入任务队列的本次任务并执行),否则进入步骤4; -
线程池不是Running状态,但从任务队列移除任务失败(可能已被某线程获取?),进入步骤4;
4、将线程池扩容至maximumPoolSize
并调用 addWorker(commond,false)
方法创建新的线程执行任务,失败则拒绝本次任务。
流程图:
源码详读:
/**
* 在将来的某个时候执行给定的任务。任务可以在新线程中执行,也可以在现有的池线程中执行。
* 如果由于此执行器已关闭或已达到其容量而无法提交任务以供执行,则由当前的{@code RejectedExecutionHandler}处理该任务。
*
* @param command the task to execute 待执行的任务命令
*/
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. 如果运行的线程少于corePoolSize,将尝试以给定的命令作为第一个任务启动新线程。
*
* 2. 如果一个任务可以成功排队,那么我们仍然需要仔细检查两点,其一,我们是否应该添加一个线程
* (因为自从上次检查至今,一些存在的线程已经死亡),其二,线程池状态此时已改变成非运行态。因此,我们重新检查状态,如果检查不通过,则移除已经入列的任务,如果检查通过且线程池线程数为0,则启动新线程。
*
* 3. 如果无法将任务加入任务队列,则将线程池扩容到极限容量并尝试创建一个新线程,如果失败则拒绝任务。
*/
int c = ctl.get();
// 步骤1:判断线程池当前线程数是否小于线程池大小
if (workerCountOf(c) < corePoolSize) {
// 增加一个工作线程并添加任务,成功则返回,否则进行步骤2
// true代表使用coreSize作为边界约束,否则使用maximumPoolSize
if (addWorker(command, true))
return;
c = ctl.get();
}
// 步骤2:不满足workerCountOf(c) < corePoolSize或addWorker失败,进入步骤2
// 校验线程池是否是Running状态且任务是否成功放入workQueue(阻塞队列)
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
// 再次校验,如果线程池非Running且从任务队列中移除任务成功,则拒绝该任务
if (! isRunning(recheck) && remove(command))
reject(command);
// 如果线程池工作线程数量为0,则新建一个空任务的线程
else if (workerCountOf(recheck) == 0)
// 如果线程池不是Running状态,是加入不进去的
addWorker(null, false);
}
// 步骤3:如果线程池不是Running状态或任务入列失败,尝试扩容maxPoolSize后再次addWorker,失败则拒绝任务
else if (!addWorker(command, false))
reject(command);
}
示例代码
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExecuteExample {
public static void main(String[] args) {
// 创建一个固定大小的线程池,包含 5 个线程
ExecutorService executorService = Executors.newFixedThreadPool(5);
// 提交任务到线程池执行
executorService.execute(() -> {
System.out.println("Task 1 is running on thread " + Thread.currentThread().getName());
});
executorService.execute(() -> {
System.out.println("Task 2 is running on thread " + Thread.currentThread().getName());
});
// 关闭线程池
executorService.shutdown();
}
}
addWorker()
addWorker(Runnable firstTask, boolean core)
方法,顾名思义,向线程池添加一个带有任务的工作线程。
参数说明:
-
Runnable firstTask:要执行的第一个任务。可以为
null
,表示新创建的线程会从任务队列中获取任务执行。 -
boolean core:一个布尔值,表示是否为核心线程。如果为
true
,则新创建的线程是核心线程;如果为false
,则新创建的线程是非核心线程。
功能描述
-
线程创建
addWorker()
方法首先会检查线程池的状态和线程数量限制,以确定是否可以创建新的线程。- 如果满足条件,它会创建一个新的
Worker
对象,这个对象封装了一个线程,并将firstTask
作为这个线程要执行的第一个任务。 - 然后,它会启动这个线程,使其开始执行任务。
-
核心线程与非核心线程的区别
- 核心线程在没有任务执行时会一直存活在线程池中,不会被回收,除非设置了允许核心线程超时。
- 非核心线程在没有任务执行一段时间后(由
keepAliveTime
参数指定)会被回收,以减少资源消耗。
执行流程:
1、外层循环判断线程池的状态是否可以新增工作线程。这层校验基于下面两个原则:
-
线程池为Running状态时,既可以接受新任务也可以处理任务
-
线程池为关闭状态时只能新增空任务的工作线程(worker)处理任务队列(workQueue)中的任务不能接受新任务
2、内层循环向线程池添加工作线程并返回是否添加成功的结果。
-
首先校验线程数是否已经超限制,是则返回false,否则进入下一步
-
通过CAS使工作线程数+1,成功则进入步骤3,失败则再次校验线程池是否是运行状态,是则继续内层循环,不是则返回外层循环
3、核心线程数量+1成功的后续操作:添加到工作线程集合,并启动工作线程
-
首先获取锁之后,再次校验线程池状态(具体校验规则见代码注解),通过则进入下一步,未通过则添加线程失败
-
线程池状态校验通过后,再检查线程是否已经启动,是则抛出异常,否则尝试将线程加入线程池
-
检查线程是否启动成功,成功则返回true,失败则进入
addWorkerFailed
方法
流程图:
源码详读:
private boolean addWorker(Runnable firstTask, boolean core) {
// 外层循环:判断线程池状态
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
/**
* 1.线程池为非Running状态(Running状态则既可以新增核心线程也可以接受任务)
* 2.线程为shutdown状态且firstTask为空且队列不为空
* 3.满足条件1且条件2不满足,则返回false
* 4.条件2解读:线程池为shutdown状态时且任务队列不为空时,可以新增空任务的线程来处理队列中的任务
*/
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
// 内层循环:线程池添加核心线程并返回是否添加成功的结果
for (;;) {
int wc = workerCountOf(c);
// 校验线程池已有线程数量是否超限:
// 1.线程池最大上限CAPACITY
// 2.corePoolSize或maximumPoolSize(取决于入参core)
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// 通过CAS操作使工作线程数+1,跳出外层循环
if (compareAndIncrementWorkerCount(c))
break retry;
// 线程+1失败,重读ctl
c = ctl.get(); // Re-read ctl
// 如果此时线程池状态不再是running,则重新进行外层循环
if (runStateOf(c) != rs)
continue retry;
// 其他 CAS 失败是因为工作线程数量改变了,继续内层循环尝试CAS对线程数+1
// else CAS failed due to workerCount change; retry inner loop
}
}
/**
* 核心线程数量+1成功的后续操作:添加到工作线程集合,并启动工作线程
*/
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
final ReentrantLock mainLock = this.mainLock;
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
// 下面代码需要加锁:线程池主锁
mainLock.lock();
try {
// 持锁期间重新检查,线程工厂创建线程失败或获取锁之前关闭的情况发生时,退出
int c = ctl.get();
int rs = runStateOf(c);
// 再次检验线程池是否是running状态或线程池shutdown但线程任务为空
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
// 线程已经启动,则抛出非法线程状态异常
// 为什么会存在这种状态呢?未解决
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w); //加入线程池
int s = workers.size();
// 如果当前工作线程数超过线程池曾经出现过的最大线程数,刷新后者值
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock(); // 释放锁
}
if (workerAdded) { // 工作线程添加成功,启动该线程
t.start();
workerStarted = true;
}
}
} finally {
//线程启动失败,则进入addWorkerFailed
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
示例代码(伪代码示例展示addWorker()
的使用场景)
class ThreadPool {
private BlockingQueue<Runnable> workQueue;
private int corePoolSize;
private int maximumPoolSize;
private volatile int workerCount;
// 其他属性和方法...
private boolean addWorker(Runnable firstTask, boolean core) {
// 检查线程池状态是否允许添加线程
if (!isPoolStateValid()) {
return false;
}
int wc = workerCount;
// 判断是否超过线程数量限制
if (core && wc >= corePoolSize || (!core && wc >= maximumPoolSize)) {
return false;
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = new Worker(firstTask);
final Thread t = w.thread;
if (t!= null) {
ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 再次检查线程池状态
if (isPoolStateValid() && (core || workQueue.size() > 0)) {
wc++;
workerCount = wc;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start();
workerStarted = true;
}
}
return workerStarted;
}
private boolean isPoolStateValid() {
// 判断线程池状态是否有效(例如,线程池未被关闭等)
return true;
}
}
class Worker implements Runnable {
final Thread thread;
Runnable firstTask;
// 其他属性和方法...
Worker(Runnable firstTask) {
this.firstTask = firstTask;
this.thread = new Thread(this);
}
public void run() {
// 线程执行的逻辑
}
}
Worker
类的定义
Worker类是内部类,既实现了Runnable,又继承了AbstractQueuedSynchronizer
(以下简称AQS),所以其既是一个可执行的任务,又可以达到锁的效果。
private final class Worker extends AbstractQueuedSynchronizer implements Runnable
主要属性
- f
inal Thread thread
:表示实际执行任务的线程 Runnable firstTask
:创建Worker
实例时传入的第一个任务,可能为null
。如果不为null
,新创建的线程会首先执行这个任务
构造方法
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
构造方法中初始化了firstTask
和thread
。同时将状态设置为-1
,以阻止在runWorker
方法执行之前被中断。
四、主要方法
-
run()
方法:- 这是
Worker
实现的Runnable
接口的方法。当线程启动时,会执行这个方法。 - 在
run()
方法中,调用了ThreadPoolExecutor
的runWorker
方法,将当前Worker
实例作为参数传入。这个方法会不断地从任务队列中获取任务并执行,直到线程池被关闭或者没有任务可执行。
- 这是
-
tryLock()
和unlock()
方法:- 重写了
AbstractQueuedSynchronizer
的这两个方法,用于实现线程的中断控制。 - 在
tryLock()
方法中,如果当前线程的中断状态为false
,则返回true
,表示获取锁成功;否则返回false
。 - 在
unlock()
方法中,总是返回true
,因为这里并不真正需要实现锁的释放操作,只是为了满足AbstractQueuedSynchronizer
的接口要求。
- 重写了
五、作用和意义
- 线程复用
Worker
类的主要作用是实现线程的复用。当一个任务被提交到线程池时,如果有空闲的Worker
,则可以直接使用这个Worker
的线程来执行任务,而不需要创建新的线程。这样可以减少线程创建和销毁的开销,提高性能。
- 任务执行
Worker
类负责执行任务。它从任务队列中获取任务,并在自己的线程中执行任务。如果任务执行过程中出现异常,Worker
会处理异常并继续从任务队列中获取任务执行。
- 中断控制
- 通过重写
tryLock()
和unlock()
方法,Worker
类实现了对线程中断的控制。在任务执行过程中,如果线程被中断,Worker
会根据中断状态来决定是否继续执行任务或者停止执行任务。
- 通过重写
六、示例代码(展示Worker
类的使用场景)
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Task is running on thread: " + Thread.currentThread().getName());
}
}
public class WorkerExample {
public static void main(String[] args) {
BlockingQueue<Runnable> workQueue = new java.util.concurrent.LinkedBlockingQueue<>();
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 10, TimeUnit.SECONDS, workQueue);
executor.execute(new MyRunnable());
executor.execute(new MyRunnable());
executor.shutdown();
}
}
在上述示例中,虽然没有直接使用Worker
类,但是在线程池执行任务的过程中,会创建Worker
实例来封装线程和任务。每个Worker
实例的线程会执行提交到线程池的任务。
总之,Worker
类在 Java 线程池中起着关键的作用,它实现了线程的复用和任务的执行,同时提供了对线程中断的控制,是线程池高效运行的重要组成部分。
runWorker()
可以说,runWorker(Worker w)
是线程池中真正处理任务的方法,前面的execute()
和 addWorker()
都是在为该方法做准备和铺垫。
在 Java 线程池中,runWorker()
方法是一个关键方法,用于执行提交到线程池中的任务。以下是关于这个方法的详细介绍:
一、方法签名
final void runWorker(Worker w)
二、参数说明
-
w
:一个Worker
实例,表示正在执行任务的工作线程。这个Worker
实例包含了实际执行任务的线程以及可能的第一个任务。 -
completedAbruptly
:一个布尔值,表示工作线程是否是异常退出。如果工作线程在执行任务时抛出了未捕获的异常,或者被外部强制中断,那么这个值为true
;否则为false
。
三、功能描述
-
任务执行循环:
-
runWorker()
方法会进入一个循环,不断地从任务队列中获取任务并执行。这个循环会一直持续,直到线程池被关闭或者没有任务可执行。
-
-
任务获取和执行
-
在循环中,首先会尝试从任务队列中获取一个任务。如果成功获取到任务,就会在当前线程中执行这个任务。如果任务执行过程中抛出异常,会进行适当的处理,以确保线程池的稳定性。
-
-
线程中断处理:
-
如果在任务执行过程中,当前线程被中断,
runWorker()
方法会根据中断状态来决定是否继续执行任务或者停止执行任务。如果线程被中断并且中断策略是立即停止,那么会退出循环并停止执行任务。
-
-
线程回收
-
如果任务队列为空并且当前线程不是核心线程,或者线程池处于关闭状态并且任务队列为空,那么会尝试回收当前线程。回收线程的过程包括设置线程的中断标志,并从线程池中移除当前
Worker
实例。
-
四、示例代码(伪代码示例展示runWorker()
的使用场景)
class ThreadPoolExecutor {
private final BlockingQueue<Runnable> workQueue;
// 其他属性和方法...
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = false;
try {
while (task!= null || (task = getTask())!= null) {
w.lock();
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);
}
}
private Runnable getTask() {
// 获取任务的具体实现
return null;
}
protected void beforeExecute(Thread t, Runnable r) {
// 在任务执行前的回调方法
}
protected void afterExecute(Runnable r, Throwable t) {
// 在任务执行后的回调方法
}
private void processWorkerExit(Worker w, boolean completedAbruptly) {
// 处理工作线程退出的方法
}
}
在上述伪代码中,runWorker()
方法首先从Worker
实例中获取第一个任务,如果没有第一个任务,就从任务队列中获取任务。然后在循环中执行任务,并进行相应的异常处理和回调方法调用。最后,根据任务执行的情况和线程池的状态,处理工作线程的退出。
五、注意事项
-
任务异常处理:
-
在任务执行过程中,如果出现异常,需要进行适当的处理,以确保线程池的稳定性。可以在
afterExecute()
方法中进行异常的记录和处理。
-
-
线程中断处理
-
如果线程被中断,需要根据中断策略来决定是否继续执行任务或者停止执行任务。可以在
runWorker()
方法中根据线程的中断状态来进行相应的处理。
-
-
线程池状态变化
-
在
runWorker()
方法执行过程中,线程池的状态可能会发生变化,例如从运行状态变为关闭状态。需要根据线程池的状态来决定是否继续执行任务或者停止执行任务
-
总之,runWorker()
方法是 Java 线程池中用于执行任务的核心方法,它通过循环从任务队列中获取任务并执行,实现了线程的复用和高效的任务执行。在使用线程池时,了解runWorker()
方法的工作原理对于正确处理任务执行和线程管理非常重要。
getTask()
由函数调用关系图可知,在ThreadPoolExecutor
类的实现中,Runnable getTask()
方法是为void runWorker(Worker w)
方法服务的,它的作用就是在任务队列(workQueue)中获取 task(Runnable),
getTask()
方法主要负责从线程池的任务队列中取出一个等待执行的任务,并返回给线程去执行。如果任务队列中没有任务可获取,该方法会根据线程池的状态和配置决定线程的下一步动作,比如等待新任务、回收线程等。。
参数说明:无参数
执行流程:
-
将timedOut(上次获取任务是否超时)置为false(首次执行方法,无上次,自然为false),进入一个无限循环
-
如果线程池为Shutdown状态且任务队列为空(线程池shutdown状态可以处理任务队列中的任务,不再接受新任务,这个是重点)或者线程池为STOP或TERMINATED状态,则意味着线程池不必再获取任务了,当前工作线程数量-1并返回null,否则进入步骤3
-
如果线程池数量超限制或者时间超限且(任务队列为空或当前线程数>1),则进入步骤4,否则进入步骤5。
-
移除工作线程,成功则返回null,不成功则进入下轮循环。
-
尝试用poll() 或者 take()(具体用哪个取决于timed的值)获取任务,如果任务不为空,则返回该任务。如果为空,则将timeOut 置为 true进入下一轮循环。如果获取任务过程发生异常,则将 timeOut置为 false 后进入下一轮循环。
流程图:
源码详读:
private Runnable getTask() {
// 最新一次poll是否超时
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:线程池状态SHUTDOWN、STOP、TERMINATED状态
* 条件2:线程池STOP、TERMINATED状态或workQueue为空
* 条件1与条件2同时为true,则workerCount-1,并且返回null
* 注:条件2是考虑到SHUTDOWN状态的线程池不会接受任务,但仍会处理任务
*/
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
/**
* 下列两个条件满足任意一个,则给当前正在尝试获取任务的工作线程设置阻塞时间限制(超时会被销毁?不太确定这点),否则线程可以一直保持活跃状态
* 1.allowCoreThreadTimeOut:当前线程是否以keepAliveTime为超时时限等待任务
* 2.当前线程数量已经超越了核心线程数
*/
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
// 两个条件全部为true,则通过CAS使工作线程数-1,即剔除工作线程
// 条件1:工作线程数大于maximumPoolSize,或(工作线程阻塞时间受限且上次在任务队列拉取任务超时)
// 条件2:wc > 1或任务队列为空
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
// 移除工作线程,成功则返回null,不成功则进入下轮循环
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
// 执行到这里,说明已经经过前面重重校验,开始真正获取task了
try {
// 如果工作线程阻塞时间受限,则使用poll(),否则使用take()
// poll()设定阻塞时间,而take()无时间限制,直到拿到结果为止
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
// r不为空,则返回该Runnable
if (r != null)
return r;
// 没能获取到Runable,则将最近获取任务是否超时设置为true
timedOut = true;
} catch (InterruptedException retry) {
// 响应中断,进入下一次循环前将最近获取任务超时状态置为false
timedOut = false;
}
}
}
示例代码(伪代码示例展示getTask()
的使用场景)
class MyThreadPool {
private BlockingQueue<Runnable> taskQueue;
private int corePoolSize;
private int maximumPoolSize;
// 其他属性和方法...
public Runnable getTask() {
boolean timedOut = false;
Runnable r = null;
// 检查任务队列是否为空
for (;;) {
if (r!= null || (r = pollTaskFromQueue())!= null)
return r;
// 检查线程池状态和线程数量
if (shouldReduceThreads()) {
return null;
}
try {
// 等待新任务或超时
r = taskQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS);
if (r!= null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
private Runnable pollTaskFromQueue() {
// 从任务队列中获取任务的具体实现
return taskQueue.poll();
}
private boolean shouldReduceThreads() {
// 判断是否应该减少线程数量的具体实现
return false;
}
}
processWorkerExit()
在 Java 线程池中,processWorkerExit()
方法用于处理工作线程的退出。以下是关于这个方法的详细介绍:
一、方法签名
private void processWorkerExit(Worker w, boolean completedAbruptly)
二、参数说明
-
w
:一个Worker
实例,表示退出的工作线程。 -
completedAbruptly
:一个布尔值,表示工作线程是否是异常退出。如果工作线程在执行任务时抛出了未捕获的异常,或者被外部强制中断,那么这个值为true
;否则为false
。
三、功能描述
-
线程回收
-
如果工作线程是正常退出(即
completedAbruptly
为false
),并且当前线程池的状态允许核心线程超时,那么会尝试减少线程数量,即回收当前线程。回收线程的过程包括将线程数量减一,并从工作线程集合中移除当前Worker
实例。 -
如果工作线程是异常退出,会根据线程池的状态和配置来决定是否添加一个新的工作线程来替代异常退出的线程。
-
-
任务队列调整
-
如果工作线程是正常退出,并且任务队列不是空的,那么会检查是否需要添加新的工作线程来处理任务队列中的剩余任务
-
如果工作线程是异常退出,并且任务队列中有未完成的任务,那么会将这些任务重新提交到线程池中,以便其他工作线程可以执行这些任务。
-
-
最终状态处理
-
如果线程池正在关闭,并且所有的任务都已经完成,那么会设置线程池的最终状态为
TERMINATED
,表示线程池已经完全关闭。
-
四、示例代码(伪代码示例展示processWorkerExit()
的使用场景)
class ThreadPoolExecutor {
private final BlockingQueue<Runnable> workQueue;
private volatile int corePoolSize;
private volatile int maximumPoolSize;
private volatile int poolSize;
private volatile boolean allowCoreThreadTimeOut;
// 其他属性和方法...
private void processWorkerExit(Worker w, boolean completedAbruptly) {
if (completedAbruptly) { // 异常退出
decrementWorkerCount();
if (!compareAndIncrementWorkerCount()) {
return;
}
try {
processThreadPoolFailure();
} finally {
if (!compareAndDecrementWorkerCount()) {
return;
}
}
} else { // 正常退出
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
completedTaskCount += w.completedTasks;
workers.remove(w);
decrementWorkerCount();
tryTerminate();
} finally {
mainLock.unlock();
}
if (allowCoreThreadTimeOut || poolSize > corePoolSize) {
decrementWorkerCount();
}
}
tryTerminate();
int c = ctl.get();
if (runStateLessThan(c, STOP)) {
if (!workQueue.isEmpty()) {
addWorker(null, false);
}
}
}
private void decrementWorkerCount() {
// 减少线程数量的具体实现
}
private boolean compareAndIncrementWorkerCount() {
// 比较并增加线程数量的具体实现
}
private boolean compareAndDecrementWorkerCount() {
// 比较并减少线程数量的具体实现
}
private void processThreadPoolFailure() {
// 处理线程池失败的具体实现
}
private void tryTerminate() {
// 尝试终止线程池的具体实现
}
}
在上述伪代码中,processWorkerExit()
方法根据工作线程的退出方式进行不同的处理。如果是异常退出,会尝试重新添加一个工作线程来处理未完成的任务;如果是正常退出,会根据线程池的状态和配置来决定是否回收当前线程。
五、注意事项
-
线程安全
-
processWorkerExit()
方法在操作线程池的状态和线程数量时需要保证线程安全,通常会使用锁来防止并发修改导致的数据不一致。
-
-
异常处理
-
在处理工作线程的异常退出时,需要进行适当的异常处理,以确保线程池的稳定性。可以在
processThreadPoolFailure()
方法中进行异常的记录和处理。
-
-
线程池状态变化
-
在处理工作线程的退出过程中,线程池的状态可能会发生变化,例如从运行状态变为关闭状态。需要根据线程池的状态来决定是否继续添加新的工作线程或者采取其他措施。
-
总之,processWorkerExit()
方法是 Java 线程池中用于处理工作线程退出的重要方法,它在保证线程池的性能和资源管理方面起着关键作用。