一、ThreadPoolExecutor应用
为什么使用线程池?答:降低线程创建和关闭的消耗。当我们想同时开启200个线程,自己手动创建线程,使用后再销毁,会导致资源占用过多,所以应考虑使用线程池。线程池里有很多提前备好的可用线程资源,如果需要就直接从线程池里拿;不用的时候,线程池会自动帮我们管理。
使用线程池主要有以下两个好处:
- 减少在创建和销毁线程上所花的时间以及系统资源的开销
- 不使用线程池,有可能造成系统创建大量线程而导致消耗完系统内存
JDK提供了一个类Executors,还有好多包装好的线程池,但是参数是固定的,所以常常要自己通过类ThreadPoolExecutor自定义线程池的属性。java提供的定义好的线程池:
FixedThreadPool
:创建固定大小的线程池,数量通过传入的参数决定。SingleThreadExecutor
:创建一个线程容量的线程池,所有的线程依次执行,相当于创建固定数量为 1 的线程池。CachedThreadPool
:创建可缓存的线程池,没有最大线程限制(实际上是 Integer.MAX_VALUE)。如果有空闲线程等待时间超过一分钟,就关闭该线程。ScheduledThreadPool
:创建计划 (延迟) 任务线程池, 线程池中的线程可以被设置为在特定的延迟时间之后开始执行,也可以以固定的时间重复执行(周期性执行)。SingleThreadScheduledExecutor
:创建单线程池延迟任务,创建一个线程容量的计划任务。
简单理解一下线程池的工作流:池内包括核心线程、非核心线程、阻塞队列、线程工厂。
- 当一个任务被分配给线程池,池会优先找到一个空闲的核心线程来完成这个任务。
- 如果没有空闲的核心线程,就创建或找到一个非核心线程来进行这个任务。一般我们会设置核心线程一直在池内存在,但非核心线程不会,当一个核心线程的空闲时间达到了我们设置的一个时间长度,该非核心线程就会被回收。
- 如果所有线程都在忙,并且已经达到了池内最大线程数的限制,那么就将此任务放入等待队列,等待线程空闲下来后,再来处理这些等待中的任务。
- 如果这三个地方都放满了,就要使用拒绝策略了,也就是以一定的策略来处理这个无处安放的任务。拒绝策略一般包括直接抛弃这个任务,或者将队头等待时间过长的一个任务删除,把当前新任务加入队列,后面会有详细介绍。
一个简单使用的例子:
ThreadPoolExecutor executor = new ThreadPoolExecutor( // 1.创建线程池对象
1, // 核心线程数
2, // 最大线程数量,即核心和非核心线程数量之和
500, // 非核心线程的最大空闲时间,查过这个时间,相应非核心线程就会被回收
TimeUnit.SECONDS, // 上一个参数的时间单位
new LinkedBlockingDeque<>(), // 阻塞队列设置
new ThreadFactory() { // 生产线程的工厂,实现接口ThreadFactory
@Override // 重写创建新线程的方法
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("测试-"); // 可以自定义设置新线程的名字
return t;
}
},
new ThreadPoolExecutor.AbortPolicy() // 设置拒绝策略
);
executor.execute(new Runnable() { // 2.调用execute或submit方法执行Runnable任务或Callable任务
@Override
public void run() {
System.out.println("love hy");
}
});
Future<Object> future = executor.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
return null;
}
});
future.get();
二、ThreadPoolExecutor核心参数
public ThreadPoolExecutor(int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数(非核心线程数)
long keepAliveTime, // 非核心线程的最大空闲时间
TimeUnit unit, // keepAliveTime的单位
BlockingQueue<Runnable> workQueue, // 等待队列,可以使用阻塞队列或工作队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler) // 拒绝策略
1、核心线程数
核心线程在池中的数量,核心线程会一直在池中等待任务到来,即使空闲也会一直存在,除非设置了allowCoreThreadTimeOut,核心线程才会在空闲一段时间后被回收。
到来的任务会优先分配给核心线程执行。
2、最大线程数
例如此参数设置为3,核心线程数为2,等待队列最大容量为2,此时来了5个任务,2个在被核心线程执行,2个进入等待队列,还有一个会被非核心线程执行,最大线程数就规定了最多有几个核心和非核心线程
3、keepAliveTime
非核心线程不会一直存在,此参数就是非核心线程的最大空闲时间,超过这个时间,非核心线程就会消失
4、阻塞队列
最常用的是ArrayBlockingQueue、LinkedBlockingQueue
5、线程工厂
线程池创建后,默认是空的,任务进入线程池后,线程才被创建。线程工厂使得我们程序员可以控制对于线程的创建,从而方便后续对这些线程的操作(如通过线程名字找到线程对象)
6、拒绝策略
当核心线程、非核心线程都在运行,且等待队列已经满了的时候,又来了新任务,如何拒绝这个任务。RejectedExecutionHandler是一个接口,只有一个抽象方法rejectedExecution(Runnable r, ThreadPoolExecutor executor)
,实现类有四个。实现类AbortPolicy对rejectedExecution的实现直接抛出了一个异常,意思是线程池无法处理多出的这个任务。实现类CallerRunsPolicy对rejectedExecution的实现是让main线程执行这个新线程:
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
实现类DiscardOldestPolicy重写的rejectedExecution方法将等待队列的队头弹出,将新线程推入等待队列:
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll(); // 把等待队列对头弹出
e.execute(r); // 新线程加入线程池
}
}
实现类DiscardPolicy更绝,重写方法啥也不做:
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
可以看出,拒绝策略比较好实现,业务中常常由程序员自己实现。
三、ThreadPoolExecutor执行流程
即业务线程提交任务到线程池后,任务的处理流程。
源码:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
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);
}
else if (!addWorker(command, false)) // 等待队列已经满了,且工作线程数大于最大线程数,addWorker方法执行失败(addWorker的第二个参数为false,表示把任务添加到非核心线程)
reject(command); // 拒绝此任务
}
图源:马士兵教育在b站的公开课
四、ThreadPoolExecutor线程池状态
4.1 核心属性ctl
// ctl就是个AtomicInteger类型变量,长度4字节,保证了原子性
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// ctl的结构:
// 1. 标识线程池当前状态(高三位)
// 2. 标识线程池当前的工作线程个数(低29位)
private static final int COUNT_BITS = Integer.SIZE - 3; // 32-3=29
private static final int CAPACITY = (1 << COUNT_BITS) - 1; // 工作线程最大数量:00011111 11111111 11111111 11111111
// 线程池的五种状态:
private static final int RUNNING = -1 << COUNT_BITS; // -1 = 11111111 11111111 11111111 11111111, RUNNING = 11100000 00000000 00000000 00000000
private static final int SHUTDOWN = 0 << COUNT_BITS; // 000
private static final int STOP = 1 << COUNT_BITS; // 001
private static final int TIDYING = 2 << COUNT_BITS; // 010,过渡状态
private static final int TERMINATED = 3 << COUNT_BITS; // 011
可以将ctl简单了解为一个int类型变量,但它的所有++和–操作都基于CAS实现了线程安全
// 传入ctl,计算线程池状态
private static int runStateOf(int c) { return c & ~CAPACITY; }
// 传入ctl,计算当前线程池中的工作线程个数
private static int workerCountOf(int c) { return c & CAPACITY; }
4.2 线程池状态变换
图源:马士兵教育在b站的公开课
五、ThreadPoolExecutor核心方法
1、execute方法
源码:
public void execute(Runnable command) {
if (command == null) // 非空判断
throw new NullPointerException();
int c = ctl.get(); // 获取ctl属性
if (workerCountOf(c) < corePoolSize) { // 当前池内的工作线程数小于核心线程数
// 获取核心线程并执行任务
if (addWorker(command, true)) // true就是获取核心线程
return; // 直接返回
c = ctl.get(); // 获取核心线程失败,重新获取ctl
} // 当前工作线程数已经>=核心线程数,或,核心线程获取失败
if (isRunning(c) && workQueue.offer(command)) { // 线程池状态为running且向工作队列添加成功
int recheck = ctl.get(); // 再检查一下ctl
if (! isRunning(recheck) && remove(command)) // 如果线程池状态不是running,就将新任务从工作队列移除
reject(command); // 执行拒绝策略
else if (workerCountOf(recheck) == 0) // 是running或移除失败,判断工作线程是否为0
// 工作线程数为0,但工作队列不为空
// 添加一个空任务非核心线程,是为了处理工作队列中的任务,避免Shutdown状态一直无法转换为Terminate状态
addWorker(null, false);
}
// 任务添加到工作队列失败,需要添加非核心线程去执行新任务
else if (!addWorker(command, false)) // 添加非核心线程失败(addWorker的第二个参数为false,表示把任务添加到非核心线程)
reject(command); // 拒绝此任务
}
重点:1、execute方法包含了线程池的整体执行流程,包含了避免并发出错情况的设计
2、为什么线程池会添加空任务到非核心线程中去
2、addWorker方法
添加任务到核心线程或非核心线程
private boolean addWorker(Runnable firstTask, boolean core) {
// 1、判断线程池状态
retry: // 外层for循环的标识
for (;;) {
int c = ctl.get(); // 获取ctl
int rs = runStateOf(c); // 获取池状态
if (rs >= SHUTDOWN && // 状态不为RUNNING
// 状态为shutdown,且,第一个线程对应一个空任务,且,工作队列不为空
// 同时满足上述3个条件时,需要处理队列中的任务(结合execute中的添加空任务到非核心线程)
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false; //不能添加任务到工作线程
for (;;) {
int wc = workerCountOf(c); // 获取工作线程数
if (wc >= CAPACITY || // 工作线程数>=最大值
wc >= (core ? corePoolSize : maximumPoolSize)) // 如果是核心线程,是否大于核心线程最大数;如果是非核心线程,是否大于最大工作线程数
return false; // 不可获取参数(core)要求的核心或非核心线程
if (compareAndIncrementWorkerCount(c)) // 以CAS方式对工作线程数+1,若成功就跳出外层for循环
break retry; // 跳出外层for循环
c = ctl.get(); // 重新获取ctl
if (runStateOf(c) != rs) // 判断和之前的状态是否一致
continue retry; // 若不一致,需要重新判断池状态
}
}
// 2、添加工作线程,并启动线程
boolean workerStarted = false; // 工作线程是否启动
boolean workerAdded = false; // 工作线程是否添加
Worker w = null;
try {
w = new Worker(firstTask); // new工作线程,并将任务扔进去
final Thread t = w.thread; // Worker中的Thread属性,获取时已经初始化过了
if (t != null) { // 如果new Worker成功了,t肯定不为null(健壮性判断)
final ReentrantLock mainLock = this.mainLock; // 对线程池加锁,因为如果正在添加w时,线程池被shutdown,就会造成并发问题(2个shutdown方法也会对池加锁,再shutdown)
mainLock.lock();
try {
// 重新获取池状态,如果再加锁之前池被关闭,就直接返回,下面的if不执行
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN || // 池状态为RUNNING
(rs == SHUTDOWN && firstTask == null)) { // 线程池状态为shutdown,但队列中有空任务
// 开始添加工作线程
if (t.isAlive()) // 当前线程是否正在执行(健壮性判断)
throw new IllegalThreadStateException();
workers.add(w); // workers是个HashSet,工作线程添加好了
int s = workers.size(); // 获取添加完成后的工作线程个数
if (s > largestPoolSize) // 若当前工作线程数>历史最大工作线程数
largestPoolSize = s; // 就更新历史最大工作线程数
workerAdded = true; // 添加成功
}
} finally {
mainLock.unlock(); // 释放锁
}
if (workerAdded) {
t.start(); // 启动线程
workerStarted = true; // 启动标记
}
}
} finally {
if (! workerStarted) // 如果没有启动
addWorkerFailed(w); // 补救措施
}
return workerStarted; // 返回线程是否启动
}
// 补救措施
private void addWorkerFailed(Worker w) {
final ReentrantLock mainLock = this.mainLock; // 加锁
mainLock.lock();
try {
if (w != null)
// 创建工作线程成功(代表w被加进了HashSet中)
workers.remove(w); // 从HashSet中移除
decrementWorkerCount(); // 减少计数
tryTerminate(); // 尝试关闭池
} finally {
mainLock.unlock(); // 解锁
}
}
3、Worker对象
private final class Worker
extends AbstractQueuedSynchronizer // 针对不同状态的线程池进行中断
implements Runnable // 存储任务
{
private static final long serialVersionUID = 6138294804551838833L;
final Thread thread; // 工作线程的Thread对象,初始化时构建出来的
Runnable firstTask; // 需要执行的任务
volatile long completedTasks;
/**
* Creates with given first task and thread from ThreadFactory.
* @param firstTask the first task (null if none)
*/
Worker(Runnable firstTask) {
setState(-1); // 工作线程刚刚被初始化出来,还不允许被中断,之后将state归为0的时候,才可以中断
this.firstTask = firstTask; // Worker被new的时候,会将任务赋值给firstTask
this.thread = getThreadFactory().newThread(this); // 给Worker构建Thread对象
}
public void run() { // 实现了Runnable接口的run方法
runWorker(this);
}
// Lock methods
// Worker自己实现了基于AQS的锁机制,而不是直接使用java中的锁
// The value 0 represents the unlocked state.
// The value 1 represents the locked state.
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) {
}
}
}
}
4、runWorker方法
final void runWorker(Worker w) { // 执行任务
Thread wt = Thread.currentThread(); // 拿到当前工作线程
Runnable task = w.firstTask; // 拿到Worker中封装的任务
w.firstTask = null;
w.unlock(); // 调用了Worker中的tryRelease方法,将状态归为0,线程之后就可以被中断了
boolean completedAbruptly = true; // 标识
try {
// 两种获取任务的方式:1、第一次获取的firstTask不为空;2、execute方法里面加入的空Worker,就对应这种方式,getTask,从工作队列获取任务
while (task != null || (task = getTask()) != null) {
w.lock(); // 在shutdown状态下,线程不允许被中断(stop的时候才可以中断工作线程的运行),注意中断线程的时候也需要获取w的锁
// 下面这个if做的事就是,只要池状态为stop,就一定要中断wt
if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) // 判断线程池状态是否为stop
&& !wt.isInterrupted()) // wt尚未被中断
wt.interrupt(); // 中断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(); // state标记设为0
}
} // 整个while循环不停地从等待队列拿出任务,循环结束
completedAbruptly = false; // 异常在afterExecute中处理,如果无异常,就会来到这一句,否则仍未true,就是个标记是否出现异常的标记位
} finally {
processWorkerExit(w, completedAbruptly); // 线程退出
}
}
5、getTask方法
// 从工作队列取出任务
private Runnable getTask() {
boolean timedOut = false; //
for (;;) { // 死循环
// ====================判断线程池状态=====================
int c = ctl.get();
int rs = runStateOf(c);
// 池不为RUNNING 且 池状态为STOP
// 或
// 池为shutdown 且 工作队列为空
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount(); // 移除当前工作线程
return null; // 这里只做了数量的扣除操作,真正将task从工作队列移除在后续进行
}
// ====================判断工作线程数量=====================
int wc = workerCountOf(c);
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c)) // 基于CAS方式移除当前线程
return null;
continue;
}
// ====================从工作队列获取任务=====================
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : // 非核心走这个方法
workQueue.take(); // 核心走这个方法
if (r != null)
return r; // 拿到了任务,直接返回
timedOut = true; // 获取任务时,达到了当前工作线程的最大空闲时间,那么下次循环当前线程就会被移除
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
6、processWorkerExit方法
// 移除当前工作线程(任务)
private void processWorkerExit(Worker w, boolean completedAbruptly) {
// 正常走getTask方法,参数completedAbruptly为false,正常就会执行decrementWorkerCount
// 除此以外,会在勾子函数抛出异常,参数completedAbruptly为true,需要手动执行decrementWorkerCount
if (completedAbruptly)
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock; // 加锁
mainLock.lock();
try {
// 当前线程池一共处理了多少任务
completedTaskCount += w.completedTasks;
// 移除工作线程(任务)
workers.remove(w);
} finally {
mainLock.unlock();
}
// 尝试将线程池状态转移为过渡状态,然后是TERMINATED状态
tryTerminate();
int c = ctl.get();
if (runStateLessThan(c, STOP)) {
// 池状态为RUNING或SHUTDOWN
if (!completedAbruptly) { // 如果正常方式移除了当前工作线程
int min = allowCoreThreadTimeOut ? 0 : corePoolSize; // 核心线程数允许的最小值
if (min == 0 && ! workQueue.isEmpty()) // 若工作队列不为空,设置工作线程最小值为1
min = 1;
// 仍然有工作线程在线程池中
if (workerCountOf(c) >= min)
return; // replacement not needed
}
// 不正常方式移除任务,添加一个空任务
// 或者 工作队列不为空且没有工作线程时,再添加一个工作线程
addWorker(null, false);
}
}