目录
参考
Java多线程进阶(四十)—— J.U.C之executors框架:ThreadPoolExecutor
Java线程池实现原理及其在美团业务中的实践
【线程池简介】
1. 线程池概念
线程池的概念,是当有任务需要执行时,线程池会给该任务分配线程。如果当前没有可用线程,一般将任务放进一个队列中,直到有线程可用时,再将任务从队列中取出执行。
使用线程池的优势:
- 当有任务需要执行时,线程池会给该任务分配线程,如果当前没有可用线程,一般会将任务放进一个队列中,当有线程可用时,再从队列中取出任务并执行
- 减少系统因为频繁创建和销毁线程所带来的开销;
- 自动管理线程,对使用方透明,使其可以专注于任务的构建。
2. 使用Demo
既然上文的三个执行器都是接口,那么我们要使用它们就需要创建它们的实现类。总体来说分为两种方式:
- 通过 ThreadPoolExecutor 创建线程池;
- 通过 Executors 创建线程池;
线程池的创建方式总共包含以下 7 种(其中 6 种是通过 Executors 创建的,1 种是通过 ThreadPoolExecutor 创建的):
- ThreadPoolExecutor:最原始的创建线程池的方式,包含 7 个参数可供设置。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- Executors.newFixedThreadPool:创建一个 固定大小 的线程池,可控制并发的线程数,超出的线程会在队列中等待;
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
- corePoolSize与maximumPoolSize相同,表示线程池大小固定:只有核心线程,超出将会进入队列等待;
- Executors.newCachedThreadPool:创建一个 缓存 线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程;
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
- corePoolSize为0,表示任务直接进入队列缓存;
- maximumPoolSize为MAX_VALUE,表示允许创建的线程数无上限;
- keepAliveTime回收时间为1min;
- Executors.newSingleThreadExecutor:创建 单个线程数 的线程池,它可以保证先进先出的执行顺序;
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
- corePoolSize和maximumPoolSize都为1,表示线程池中最多只能有一个线程
- Executors.newScheduledThreadPool:创建一个可以 执行延迟任务 的线程池;
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
- Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池;
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}
- Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定)【JDK 1.8 添加】。
public static ExecutorService newWorkStealingPool() {
return new ForkJoinPool
(Runtime.getRuntime().availableProcessors(),
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null, true);
}
可见通过Executors创建线程池,其本质也是通过调用ThreadPoolExecutor调整参数值实现的
在阿里编程规范中,线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式更加明确线程池的运行规则,规避资源耗尽的风险。
Executors 返回的线程池对象的弊端如下:
- FixedThreadPool 和 SingleThreadPool:允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
- CachedThreadPool:允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
本文主要看ThreadPoolExecutor创建方式,使用Demo:
final static ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
5,
10,
1,
TimeUnit.MINUTES,
new LinkedBlockingQueue<>());
//核心线程数:5
//最大线程数:10
//非核心线程超时时间:1min
//等待队列的长度:无限制,Integer.MAX_VALUE
public static void main(String[] args) {
for (int i = 0; i < 20; ++i) {
poolExecutor.execute(new Runnable() {
public void run() {
try {
Thread.sleep(3000);
System.out.println("线程" + Thread.currentThread().getName() + "执行结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
System.out.println("pool execute over");
}
【ThreadPoolExecutor类】
1. 继承关系
- ThreadPoolExecutor实现的顶层接口是Executor,顶层接口Executor提供了一种思想:将任务提交和任务执行进行解耦。用户无需关注如何创建线程,如何调度线程来执行任务,用户只需提供Runnable对象,将任务的运行逻辑提交到执行器(Executor)中,由Executor框架完成线程的调配和任务的执行部分。
- ExecutorService接口增加了一些能力:
- 扩充执行任务的能力,补充可以为一个或一批异步任务生成Future的方法;
- 提供了管控线程池的方法,比如停止线程池的运行
- AbstractExecutorService则是上层的抽象类,将执行任务的流程串联了起来,保证下层的实现只需关注一个执行任务的方法即可。
- 最下层的实现类ThreadPoolExecutor实现最复杂的运行部分,ThreadPoolExecutor将会一方面维护自身的生命周期,另一方面同时管理线程和任务,使两者良好的结合从而执行并行任务。
2. 构造函数
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;
}
- corePoolSize:核心线程数,线程池中始终存活的线程数。
- maximumPoolSize:最大线程数。
- keepAliveTime:最大线程数可以存活的时间。
这个参数的含义可以理解为,当线程数超出了corePoolSize,那么多出的线程如果空闲的话,最长可以存活的时间,超时将被回收。即非核心线程的空闲存活时间;
如果调用了allowCoreThreadTimeOut,那么当线程数没有超出corePoolSize,这个超时时间依然生效。 - unit:线程的存活时间单位:
- workQueue:一个阻塞队列,用来存储线程池等待执行的任务,(较常用的是 LinkedBlockingQueue 和 Synchronous,线程池的排队策略与 BlockingQueue 有关)
- threadFactory:线程工厂,主要用来创建线程。
- handler:拒绝策略,(默认策略为 AbortPolicy):
AbortPolicy:拒绝并抛出异常。
CallerRunsPolicy:使用当前调用的线程来执行此任务。
DiscardOldestPolicy:抛弃队列头部(最旧)的一个任务,并执行当前任务。
DiscardPolicy:忽略并抛弃当前任务。
概念:核心线程池、非核心线程池
ThreadPoolExecutor将线程池分为了两个逻辑上的部分:核心线程池和非核心线程池。
- 核心线程池:大小为corePoolSize
- 非核心线程池:大小为maximumPoolSize - corePoolSize
3. 内部变量
private final BlockingQueue<Runnable> workQueue;
private final ReentrantLock mainLock = new ReentrantLock();
private final HashSet<Worker> workers = new HashSet<Worker>();
private final Condition termination = mainLock.newCondition();
private volatile int corePoolSize;
private volatile int maximumPoolSize;
private volatile ThreadFactory threadFactory;
- workQueue:以阻塞队列保存的任务执行列表
- workers:保存工作线程的集合
- threadFactory:thread在调用构造方法时通过ThreadFactory来创建线程
- corePoolSize:核心线程池大小
- maximumPoolSize:最大线程池大小
4. 内部类:工作线程Worker
当我们向线程池提交一个任务时,将创建一个工作线程——Worker。其本质也就是实现了Runnable接口的线程,结构如下:
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
/**
* This class will never be serialized, but we provide a
* serialVersionUID to suppress a javac warning.
*/
private static final long serialVersionUID = 6138294804551838833L;
/** Thread this worker is running in. Null if factory fails. */
final Thread thread;
/** Initial task to run. Possibly null. */
Runnable firstTask;
/** Per-thread task counter */
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); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
/** Delegates main run loop to outer runWorker */
public void run() {
runWorker(this);
}
// Lock methods
//
// 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) {
}
}
}
}
Worker是ThreadPoolExecutor的内部类,实现了AQS的框架。
【线程池的运行】
1. 运行机制
- 线程池在内部实际上构建了一个生产者消费者模型,将线程和任务两者解耦,并不直接关联,从而良好的缓冲任务,复用线程。
- 线程池的运行主要分成两部分:任务管理、线程管理。
- 任务管理部分充当生产者的角色,当任务提交后,线程池会判断该任务后续的流转:
- (1)直接申请线程执行该任务;
- (2)缓冲到队列中等待线程执行;
- (3)拒绝该任务。
- 线程管理部分是消费者,它们被统一维护在线程池内,根据任务请求进行线程的分配,当线程执行完任务后则会继续获取新的任务去执行,最终当线程获取不到任务的时候,线程就会被回收。
2. 线程池状态:生命周期
ThreadPoolExecutor中的变量:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// 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;
- 线程池状态的保存——使用AtomicInteger类型的变量ctl
- 低29位保存线程数
- 高3位保存线程池状态
- 线程池状态
- running : 接受新任务, 且处理已经进入阻塞队列的任务
- shutdown : 不接受新任务, 但处理已经进入阻塞队列的任务
- stop : 不接受新任务, 且不处理已经进入阻塞队列的任务, 同时中断正在运行的任务
- tidying : 所有任务都已终止, 工作线程数为0, 线程转化为TIDYING状态并准备调用terminated方法
- terminated : terminated方法已经执行完成
线程池的生命周期:
【任务管理】
1. 任务提交——execute
我们实现了一个Runnable中的内容,会直接调用execute方法进行提交:
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))
reject(command);
}
- workerCount:线程池中的工作线程数
- corePoolSize核心线程池上限
- maximumPoolSize最大线程池上限
- case1:workerCount < corePoolSize :
创建并启动一个新线程,执行新提交的任务; - case2:workerCount >= corePoolSize,且阻塞队列未满
插入任务至阻塞队列 - case3:maximumPoolSize > workerCount >= corePoolSize ,且阻塞队列已满
创建并启动一个线程来执行新提交的任务 - case4:workerCount >= maximumPoolSize,且阻塞队列已满
根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常
2. Worker线程创建——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.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
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);
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());
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 {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
参数:
- firstTask :
- 如果指定了该参数, 表示将立即 创建一个新工作线程执行 该firstTask任务;
- 否则复用已有的工作线程,从 工作队列中获取任务 并执行
- (可以看到上述execute方法中,只有「workerCount >= corePoolSize,且阻塞队列未满」这一条件下,将任务插入阻塞队列,addWorker方法的firstTask参数才为null)
- core [执行任务的工作线程归属于哪个线程池]:
- true 核心线程池
- false 非核心线程池
execute中的场景:
- workerCount < corePoolSize,参数 [ firstTask:command、core:true ]
- workerCount >= corePoolSize,且阻塞队列未满,参数 [ firstTask:null、core:false ]
- workerCount < maximumPoolSize,参数 [ firstTask:command、core:false ]
流程:
- 检查线程池的状态
- 线程池状态 = STOP / TIDYING / TERMINATED:不再接受任务,直接返回
- 线程池状态 = SHUTDOWN / STOP / TIDYING / TERMINATED 且 firstTask != null:
因为当线程池状态≥ SHUTDOWN时, 不再接受新任务的提交,所以直接返回 - 线程池状态 = SHUTDOWN / STOP / TIDYING / TERMINATED 且 阻塞队列为空:
队列中已经没有任务了, 所以也就不需要执行任何任务了,可以直接返回
- 判断工作线程数是否超限
- 工作线程数超过最大工作线程数(2^29-1)
- 工作线程数超过核心线程池上限(入参core为true, 表示归属核心线程池)
- 工作线程数超过总线程池上限(入参core为false, 表示归属非核心线程池)
- 条件都符合,workers.add(),将传入的firstTask包装为Worker类,然后加入到保存工作线程的HashSet中。
3. 线程的获取及运行——runWorker方法
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
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
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);
}
}
- 获取firstTask,如果为null,则在while循环中通过getTask获取;
- 如果线程池正在停止,那么要保证当前线程是中断状态,否则要保证当前线程不是中断状态。
- 执行任务
- 如果getTask结果为null则跳出循环,执行processWorkerExit()方法,销毁线程
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.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
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;
}
}
}
- for循环尝试获取线程
- 如果线程池状态为shutdown,或者为stop且workQueue为空,那么返回null
- 获取线程池当前线程数wc,判断线程数是否过多,如果是返回null
- 通过workQueue.poll或workQueue.take限时或阻塞地获取任务,返回
4.任务拒绝——reject
final void reject(Runnable command) {
handler.rejectedExecution(command, this);
}
handler为RejectedExecutionHandler接口,有四个实现类
各个拒绝策略的描述如下: