在互联网的开发场景下,很多业务场景下我们需要使用到多线程的技术,从 Java 5 开始,JDK中 提供了线程池的实现,线程池就是一个线程的容器,每次只执行额定数量的线程。java.util.concurrent包中提供了线程池类ThreadPoolExecutor类来管理线程,同时提供了工厂类Executors简化线程池的使用。
本篇,我们将来详细的理解一下ThreadPoolExecutor的使用方式及其内部核心实现机制,让你明明白白的使用好线程池,Let’s do it!
在执行一个异步任务或并发任务时,往往是通过直接new Thread()方法来创建新的线程,这样做弊端较多,Java的线程体系,是基于操作系统的线程来进行实现的,不做控制的大量的创建线程,会极大的消耗操作系统资源。
因此,更好的解决方案是合理地利用线程池,线程池的优势很明显,具体如下:
java.util.concurrent包中提供了多种线程池的创建方式,我们可以直接使用ThreadPoolExecutor类直接创建一个线程池,也可以使用工厂类Executors创建,下面我们分别了解一下这几种创建的方式。
Executors类是java.util.concurrent提供的一个创建线程池的工厂类,使用该类可以方便的创建线程池,此类提供的几种方法,支持创建四种类型的线程池,分别是:newCachedThreadPool、newFixedThreadPool、newScheduledThreadPool、newSingleThreadExecutor。
创建一个可缓存的无界线程池,该方法无参数。当线程池中的线程空闲时间超过60s则会自动回收该线程,当任务超过线程池的线程数则创建新线程。
线程池的大小上限为Integer.MAX_VALUE,可看做是无限大。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0,
Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
使用示例:
/**
* 创建无边界大小的线程池
*/
public static void createCachedThreadPool() {
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
final CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i = 0; i < 10; i++) {
final int currentIndex = i;
cachedThreadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + ", currentIndex is : " + currentIndex);
countDownLatch.countDown();
});
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("全部线程执行完毕");
}
创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。在任意点,在大多数 nThreads 线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads,
nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
使用示例:
/**
* 创建固定大小的线程池
*/
public static void createFixedThreadPool() {
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
final CountDownLatch countDownLatch = new CountDownLatch(5);
for (int i = 0; i < 5; i++) {
final int currentIndex = i;
fixedThreadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + ", currentIndex is : " + currentIndex);
countDownLatch.countDown();
});
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("全部线程执行完毕");
}
创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
使用示例:
这里创建了一个调度的线程池,执行两个任务,第一个任务延迟1秒后执行,第二个任务为周期性任务,延迟2秒后,每三秒执行一次
/**
* 创建给定延迟后运行命令或者定期地执行的线程池
*/
public static void createScheduledThreadPool() {
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
final CountDownLatch countDownLatch = new CountDownLatch(5);
for (int i = 0; i < 5; i++) {
final int currentIndex = i;
//定时执行一次的任务,延迟1s后执行
scheduledThreadPool.schedule(() -> {
System.out.println(Thread.currentThread().getName() + ", currentIndex is : " + currentIndex);
countDownLatch.countDown();
}, 1, TimeUnit.SECONDS);
//周期性地执行任务,延迟2s后,每3s一次地周期性执行任务
scheduledThreadPool.scheduleAtFixedRate(() -> System.out.println(Thread.currentThread().getName() + ", every 3s execute done."), 2, 3, TimeUnit.SECONDS);
}
}
执行结果:
pool-1-thread-1, currentIndex is : 0
pool-1-thread-1, currentIndex is : 1
pool-1-thread-4, currentIndex is : 4
pool-1-thread-3, currentIndex is : 3
pool-1-thread-2, currentIndex is : 2
pool-1-thread-5, every 3s execute done.
pool-1-thread-3, every 3s execute done.
pool-1-thread-4, every 3s execute done.
pool-1-thread-2, every 3s execute done.
pool-1-thread-1, every 3s execute done.
pool-1-thread-5, every 3s execute done.
pool-1-thread-4, every 3s execute done.
pool-1-thread-5, every 3s execute done.
pool-1-thread-3, every 3s execute done.
pool-1-thread-2, every 3s execute done.
可以看到,第一个任务执行完毕后,开始执行定时调度型任务,按照指定的时间周期执行。
该线程池提供了多个方法:
方法 | 说明 |
---|---|
schedule(Runnable command, long delay, TimeUnit unit) | 延迟一定时间后执行Runnable任务 |
schedule(Callable callable, long delay, TimeUnit unit) | 延迟一定时间后执行Callable任务 |
scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) | 延迟一定时间后,以间隔period时间的频率周期性地执行任务 |
scheduleWithFixedDelay(Runnable command, long initialDelay, long delay,TimeUnit unit) | 与scheduleAtFixedRate()很类似,但是不同的是scheduleWithFixedDelay()的周期时间间隔是以上一个任务执行结束到下一个任务开始执行的间隔,而scheduleAtFixedRate()方法的周期时间间隔是以上一个任务开始执行到下一个任务开始执行的间隔,也就是这一些任务系列的触发时间都是可预知的 |
创建一个单线程的线程池,以无界队列方式来运行该线程。当多个任务提交到单线程线程池中,线程池将逐个去进行执行,未执行的任务将放入无界队列进行等待。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService(
new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));
}
使用示例:
/**
* 创建单线程的线程池
*/
public static void createSingleThreadPool() {
ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
singleThreadPool.execute(()-> System.out.println(Thread.currentThread().getName()));
}
线程池方法 | 初始化线程池数 | 最大线程池数 | 线程池中线程存活时间 | 时间单位 | 工作队列 |
---|---|---|---|---|---|
newCachedThreadPool | 0 | Integer.MAX_VALUE | 60 | 秒 | SynchronousQueue |
newFixedThreadPool | 入参指定大小 | 入参指定大小 | 0 | 毫秒 | LinkedBlockingQueue |
newScheduledThreadPool | 入参指定大小 | Integer.MAX_VALUE | 0 | 微秒 | DelayedWorkQueue |
newSingleThreadExecutor | 1 | 1 | 0 | 毫秒 | LinkedBlockingQueue |
以上,我们介绍了通过工具类Executors创建线程池的几种方式,Executors实际上是JDK封装好的线程池工具类,可以让开发者简单方便的创建线程池,而无需关心内在细节,事实上,Executors中的这些方法最终都是通过ThreadPoolExecutor类来完成的,当有一些场景需要更细粒度的控制的线程池,可以使用ThreadPoolExecutor方法创建线程池。
/**
* 使用ThreadPoolExecutor创建线程池
*/
public void createThreadPoolExecutor() {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 10L, TimeUnit.SECONDS,
new LinkedBlockingDeque<>(1000),
new ThreadPoolExecutor.AbortPolicy());
final CountDownLatch countDownLatch = new CountDownLatch(8);
for (int i = 0; i < 8; i++) {
final int currentIndex = i;
System.out.println("提交第" + i + "个线程");
threadPoolExecutor.execute(() -> {
System.out.println(Thread.currentThread().getName() + ", currentIndex is : " + currentIndex);
countDownLatch.countDown();
});
}
System.out.println("全部提交完毕");
try {
System.out.println("准备等待线程池任务执行完毕");
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("全部线程执行完毕");
}
接下来看一下ThreadPoolExecutor的中的各个参数的含义。
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;
}
ThreadPoolExecutor提供了四个构造方法,我们可以选择要传入的参数,如果不传入,则会默认指定,来分别看一下这些参数含义:
corePoolSize:核心线程数,即线程池中一直存活着的线程的最小数量。需要注意的是,核心线程数并不代表线程池启动后,就会立即创建这么多线程,只有当线程池接收到我们提交给他的任务后, 它才会去创建并启动一定数量的核心线程来执行这些任务。这种默认的核心线程的创建启动机制,有助于降低系统资源的消耗。
maximumPoolSize:线程池中允许创建的最大线程数量,当线程池的当前的核心线程全部在运行中,此时线程池接收到新的执行任务后,首先会判断线程池内部的阻塞队列 workQueue 中是否还有空间,如果有,则会将任务放入阻塞队列中,否则,会创建新的线程去进行执行;当线程数达到了最大线程数后,线程池接收到新的任务后,将会触发拒绝策略。
keepAliveTime:当线程池中的线程数大于核心线程数是,如果一个线程处于空闲状态,超过keepAliveTime 时间将终止该线程。这个参数的设定,需要考虑具体情况:如果要执行的任务相对较多,并且每个任务执行的时间比较短,那么可以为该参数设置一个相对较大的数值,以提高线程的利用率。
如果执行的任务相对较少, 线程池使用率相对较低, 那么可以先将该参数设置为一个较小的数值, 通过超时停止的机制来降低系统线程资源的开销, 后续如果发现线程池的使用率逐渐增高以后, 线程池会根据当前提交的任务数自动创建新的线程。
unit:keepAliveTime的时间单位。
workQueue:线程池中存放执行任务的队列,用于保存等待执行的任务。当提交一个新的任务到线程池以后, 线程池会根据当前池子中正在运行着的线程的数量, 指定出对该任务相应的处理方式,主要有以下几种处理方式:
1、如果线程池中正在运行的线程数少于核心线程数,那么线程池总是倾向于创建一个新线程来执行该任务,而不是将该任务提交到该队列 workQueue 中进行等待。
2、如果线程池中正在运行的线程数不少于核心线程数,那么线程池总是倾向于将该任务先提交到队列 workQueue 中先让其等待,而不是创建一个新线程来执行该任务。
3、如果线程池中正在运行的线程数不少于核心线程数,并且线程池中的阻塞队列也满了使得该任务入队失败,那么线程池会去判断当前池子中运行的线程数是否已经等于了该线程池允许运行的最大线程数 maximumPoolSize。如果发现已经等于了,说明池子已满,无法再继续创建新的线程了,那么就会拒绝执行该任务。如果发现运行的线程数小于池子允许的最大线程数,那么就会创建一个线程(这里创建的线程是非核心线程)来执行该任务。
排队策略:
handler:拒绝策略,当向线程池提交新的任务,线程池无法执行的时候,将会触发拒绝策略。当出现以下情况时,拒绝策略将会触发:
ThreadPoolExecutor支持四种拒绝策略,分别是:
1、AbortPolicy: 直接拒绝策略,抛出 RejectedExecutionException 异常,该策略也是线程池的默认拒绝策略。
2、CallerRunsPolicy:将被拒绝的任务放在ThreadPoolExecutor.execute()方法所在的那个线程中执行。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。
3、DiscardPolicy:将被拒绝的任务直接删除。
4、DiscardOldestPolicy:当线程池没有关闭的情况下,会将阻塞队列队首的那个任务从队列中移除,然后将被拒绝的任务加入队列的队尾。
ThreadPoolExector提供了shutdown()或shutdownNow()方法来关闭线程池。
中断采用interrupt方法,所以无法响应中断的任务可能永远无法终止。但调用上述的两个关闭之一,isShutdown()方法返回值为true,当所有任务都已关闭,表示线程池关闭完成,则isTerminated()方法返回值为true。当需要立刻中断所有的线程,不一定需要执行完任务,可直接调用shutdownNow()方法。
ThreadPoolExector提供的多个方法,可以用于监控线程池的运行情况,参数如下:
通过扩展线程池进行监控:继承线程池并重写线程池的beforeExecute(),afterExecute()和terminated()方法,可以在任务执行前、后和线程池关闭前自定义行为。如监控任务的平均执行时间,最大执行时间和最小执行时间等。
使用ThreadPoolExecutor直接创建线程池时,可以使用第三方的ThreadFactory,或者自己实现ThreadFactory接口,拓展更多的属性,例如设置线程名称、执行开始时间、优先级等等。
在上述的篇幅中,我们详尽的介绍了ThreadPoolExector线程池的使用方式,但了解线程池的使用方式,也需要了解线程池的实现与工作原理,才可以知其然也知其所以然,接下来,我们来对线程池的源码进行分析。
分析源码之前,下面我们用一张流程图对ThreadPoolExector的工作流程进行概览:
在分析线程池工作原理的源码之前,我们先来看一下线程池内部的一些核心参数:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
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;
private static int runStateOf(int c) {
return c & ~CAPACITY;
}
private static int workerCountOf(int c) {
return c & CAPACITY;
}
private static int ctlOf(int rs, int wc) {
return rs | wc;
}
ctl参数是线程池内部非常核心的一个参数,使用AtomicInteger表示,保证多线程操作下的线程安全性;
它控制着线程池的状态,一个ctl变量包含了两部分信息:线程池的运行状态和线程池内有效线程的数量。ctl是一个int类型,它用其高3位表示线程池运行状态,用低29位表示线程池内有效线程的数量。
关于这种控制方式,如果您看过ReentrantReadWriteLock的源码,一定不陌生这种方式,ReentrantReadWriteLock使用一个数值分别记录读写锁的获取次数,这种使用位操作控制的方式在concurrent包中非常常见,非常精妙。
线程池的运行状态分为以下几种状态:
通过调用runStateOf() 方法,可以获取到当前线程池的运行状态
调用workerCountOf() 方法,可以获取到当前线程池的工作线程数目
对于我们来讲,我们可能更关注的状态是线程池是否在运行、是否准备停止、是否已经停止,
这里我们可以粗暴的预先给出结果:
runStateOf() < 0 时,表示线程池正在运行中
runStateOf() = 0 时,表示线程池准备停止
runStateOf() > 0 时,表示线程池进入停止状态(也可能是TIDYING或者TERMINATED)
Worker是内部比较重要的一个对象,它是线程池内部的实际执行器,我们提交的Runnable方法,是由它进行执行的。Worker对象实现了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 firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
public void run() {
runWorker(this);
}
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) {
}
}
}
}
介绍完构造方法与内部结构的核心参数与对象,我们接下来看一下线程池的核心实现。
execute()方法是线程池的核心方法,提交一个Runnable任务给线程池,线程池进行执行,接下来我们来看分析一下实现机制:
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);
}
上面就是execute() 方法的实现,其接收一个Runnable的参数,我们对大体流程进行梳理,在对具体细节进行分析:
1、获取ctl值
2、判断当前工作线程池数是否小于核心线程数,如果是,新增工作线程执行任务,并结束流程
3、如果不是,说明当前工作线程数已经达到核心线程数值,则判断线程池是否是运行状态,如果是,尝试将任务加入到阻塞队列中(而不是新增线程);
如果成功,再次判断线程池是否是运行状态,如果已经不再是运行状态,将刚刚新增到阻塞队列中的任务移除,并执行拒绝策略;如果依旧是运行状态,判断当前工作线程数是否为0,如果是,增加工作线程,但不启动;
4、上述条件均不满足,则尝试新增工作线程执行任务,如果失败,执行拒绝策略
我们来分步骤进行分析:
首先来看一下新增工作线程的实现,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 {
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;
}
流程稍长,我们一步一步来看:
1、进入无限循环,首先获取运行状态,判断运行状态是否正常
2、循环检查当前工作线程数是否大于最大容量,或者是否大于核心线程数或最大线程数(参数控制),如果校验通过,通过CAS操作增加工作线程数,并退出循环
3、前置校验全部通过后,进入新增工作线程部分,创建Worker对象,将提交的任务放入Worker中
4、获取锁,将Worker加入工作线程组中,释放锁,启动Worker线程
addWorker() 方法的大体流程如上,这里并没有进行特别详细的分析,由于整体流程并不复杂,较为核心的部分,是Worker中执行提交任务的部分:
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 ((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);
}
}
由于Worker本身是一个线程,当启动Worker时,会执行其run() 方法,在run() 中,调用了runWorker(),实现如上。
我们对其流程进行详细分析:
1、获取当前线程,以及Worker中的Runnable任务
2、获取锁,如果任务不为空,或getTask() 获取任务不为空时,进行while循环
3、首先会判断线程池的状态,以及任务线程的状态,是否处于被打断的状态
4、执行扩展方法beforeExecute(),在执行任务前需要进行的操作,ThreadPoolExecutor只定义了方法,没有进行实现,子类可以自行进行扩展
5、执行任务的run方法,真正的任务内容开始执行
6、执行扩展方法afterExecute(),同beforeExecute()
7、释放锁,将任务的引用置空,并进行任务执行结束后的状态处理
这是一个较为就简单的示意图,当我们提交一个任务到ThreadPoolExecutor线程池时,如果当前线程池没有工作线程,则会新建一个,去执行提交的任务,当工作线程数达到核心线程数时,再提交的任务会加入到阻塞队列中去;
当核心工作线程执行完毕它之前的任务时,则会从阻塞队列中获取任务,再次进行执行,而获取的操作,则是在getTask()方法中实现:
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
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;
}
}
}
getTask() 主要流程我们看一下:
1、获取线程池运行状态
2、检查线程池的状态,如果已经是STOP及以上的状态,或者已经SHUTDOWN,队列也是空的时候,直接return null,并将Worker数量-1
3、获取从阻塞队列获取任务是否超时标志,allowCoreThreadTimeOut参数,字面意思是否允许核心线程超时,即如果我们设置为false,那么只有当线程数wc大于corePoolSize的时候才会超时,更直接的意思就是,如果设置allowCoreThreadTimeOut为false,那么线程池在达到corePoolSize个工作线程之前,不会让闲置的工作线程退出,如果设置为true,那么核心工作线程空闲后,会立刻退出
4、从队列中取任务,根据timed选择是有时间期限的等待还是无时间期限的等待,这里的等待时间是我们在构造方法中传入的keepAliveTime
看到这里,我们就可以理解在文章开头中提到的,为什么线程池可以重用已存在的线程: 因为当工作线程达到核心线程数目后,如果当前线程池中没有需要执行的任务,线程池将进入空闲状态,核心工作线程将进入等待;此时当有新任务提交时,将会进入到阻塞队列中,核心线程会从阻塞队列中获取任务,进行执行,反复这个过程。经过上述对线程池大体工作流程的介绍,我们已经了解了线程池大体的工作机制,接下来我们进入最后一个部分,线程池无可执行任务后,线程池中线程的销毁流程。
线程池中线程的销毁依赖JVM自动的回收,线程池做的工作是根据当前线程池的状态维护一定数量的线程引用,防止这部分线程被JVM回收,当线程池决定哪些线程需要回收时,只需要将其引用消除即可。
Worker被创建出来后,就会不断地进行轮询,然后获取任务去执行,核心线程可以无限等待获取任务,非核心线程要限时获取任务。当Worker无法获取到任务,也就是获取的任务为空时,循环会结束,Worker会主动消除自身在线程池内的引用。
下面我们来看一下线程池销毁空闲线程的具体实现:
private void processWorkerExit(Worker w, boolean completedAbruptly) {
// 1、判断流程异常标志位,如果出现用户的执行任务中抛出异常打断流程,更新workerCount - 1
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
// 2、获取锁资源
mainLock.lock();
try {
// 3、更新completedTaskCount,并将Worker移除工作队列
completedTaskCount += w.completedTasks;
workers.remove(w);
} finally {
mainLock.unlock();
}
// 4、尝试终止线程池,检查线程池是否需要进入终止流程
tryTerminate();
// 5、是否需要增加worker线程
int c = ctl.get();
// 如果状态是running、shutdown,即tryTerminate()没有成功终止线程池,尝试再添加一个worker
if (runStateLessThan(c, STOP)) {
// 判断流程异常标志位为false的情况,即没有task任务可以获取而完成的,计算min,并根据当前worker数量判断是否需要addWorker()
if (!completedAbruptly) {
// allowCoreThreadTimeOut默认为false,即min默认为corePoolSize
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
// 如果min为0,即不需要维持核心线程数量,且workQueue不为空,至少保持一个线程
if (min == 0 && ! workQueue.isEmpty())
min = 1;
// 如果线程数量大于最少数量,直接返回,否则下面至少要addWorker一个
if (workerCountOf(c) >= min)
return; // replacement not needed
}
// 添加一个没有firstTask的worker
// 只要Worker是completedAbruptly流程异常终止的,或者线程数量小于要维护的数量,就新添一个Worker线程,即使是shutdown状态
addWorker(null, false);
}
}
processWorkerExit()方法在runWorker()方法的finally代码块中,当工作队列中没有待执行的任务时,线程池将会执行回收Worker线程的操作,主要流程如下:
1、判断流程异常标志位,如果出现用户的执行任务中抛出异常打断流程,更新workerCount - 1
2、获取锁资源
3、更新completedTaskCount,并将Worker移除工作队列
4、尝试终止线程池,检查线程池是否需要进入终止流程
5、检查是否需要增加worker线程,这里分为两部分逻辑:
综上,我们对线程池的整体工作流程的核心源码实现进行了了解,简单的说,ThreadPoolExecutor实现了一套生产消费者模型,类比于一条工厂流水线,流水线上的工人们(Worker)等待着流水线(BlockingQueue)上流转的零件(Runnable)来进行组装工作,流水线上零件较多,大于额定的负载量时,就会增派人手一起过来帮忙,流水线上零件较少时,则会让一部分工人们下去休息,希望用这样的例子来帮助读者更好的理解线程池的工作流程。
在实际业务开发场景中,我们可能会遇见很多方法需要使用异步的处理方式,但在业务代码中多处创建线程池有时会有些繁琐,针对这种场景,我们可以将线程池交予Spring进行托管,仅需在需要使用异步的方法时,使用@Async注解,即可执行异步操作。
SpringBoot开启@Async:
@Configuration
@EnableAsync
public class AsyncConfig {
...
}
使用@Async:
@Async //标注使用
public void asyncMethodWithVoidReturnType() {
System.out.println("Execute method asynchronously. "+ Thread.currentThread().getName());
}
默认情况下,Spring会使用SimpleAsyncTaskExecutor开启异步,但并不推荐使用这种方式,因为SimpleAsyncTaskExecutor并非使用线程池处理异步,而是每次创建一个线程来进行处理。
#SimpleAsyncTaskExecutor#doExecute()
/**
* Template method for the actual execution of a task.
* The default implementation creates a new Thread and starts it.
* @param task the Runnable to execute
* @see #setThreadFactory
* @see #createThread
* @see java.lang.Thread#start()
*/
protected void doExecute(Runnable task) {
Thread thread = (this.threadFactory != null ? this.threadFactory.newThread(task) : createThread(task));
thread.start();
}
推荐方式:
我们可以自行指定异步线程池:
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean(name = "threadPoolTaskExecutor")
public Executor threadPoolTaskExecutor() {
return new ThreadPoolTaskExecutor();
}
}
@Async("threadPoolTaskExecutor")
public void asyncMethodWithConfiguredExecutor() {
System.out.println("Execute method with configured executor - " + Thread.currentThread().getName());
}
也可以通过实现AsyncConfigurer接口的方式自定义线程池:
@Configuration
@EnableAsync
public class SpringAsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
return new ThreadPoolTaskExecutor();
}
}
在使用Dubbo提供RPC接口时,如果某些Dubbo Service API非常重要,我们希望将其与其他接口隔离开,来保障性能,可以为单独的Dubbo接口指定线程池。
Dubbo Service支持指定provider,在provider中配置指定自定义线程池,即可完成RPC接口的单独线程池隔离。
示例:
// 指定provider配置中的threadpool属性,我们自定义的线程池
<dubbo:provider id="p1" threadpool="myThreadPool" default=false/>
实现自定义线程池:
src
|-main
|-java
|-com
|-xxx
|-XxxThreadPool.java (实现ThreadPool接口)
|-resources
|-META-INF
|-dubbo
|-org.apache.dubbo.common.threadpool.ThreadPool (纯文本文件,内容为:myThreadPool=com.xxx.XxxThreadPool)
import org.apache.dubbo.common.threadpool.ThreadPool;
import java.util.concurrent.Executor;
public class XxxThreadPool implements ThreadPool {
public Executor getExecutor() {
// ...
}
}
Dubbo SPI配置自定义线程池 META-INF/dubbo/org.apache.dubbo.common.threadpool.ThreadPool:
myThreadPool=com.xxx.XxxThreadPool
在DubboService接口中指定provider:
@Service
@DubboService(version = "1.0.0", timeout = 1000, provider = "p1")
@Slf4j
public class DubboApiImpl implements DubboApi {
@Override
public String hello() {
return "hello world";
}
}
本篇,我们介绍了ThreadPoolExecutor的使用方式及其工作原理,线程池在我们的日常业务场景中的应用非常的广泛,希望可以对您有所帮助。