线程池
一、线程池简介
线程池可以看做是线程的集合。当请求到来,线程池给这个请求分配一个空闲的线程,任务完成后回到线程池中等待下次任务(而不是销毁),实现了线程的重用。
没有线程池 ——> 为每个请求都新开一个线程 缺点:
- 线程生命周期的开销非常高(创建和销毁都是需要时间和资源的)
- 程序的稳定性和健壮性会下降。线程池的优点:
- 降低资源消耗
- 提高响应速度
- 提高线程的可管理性:线程池允许我们开启多个任务而不用为每个线程设置属性值(便于管理);线程池根据当前在系统中运行的进程来优化线程时间片(调优);线程池可以限制创建线程数量,提高系统稳定性。
- 线程池工作原理:
如果核心线程池的线程执行完当前任务,那么立刻从阻塞队列头取任务来执行。
如果队列为空,且当前线程超过了存活时间(keepAliveTime),那么判断当前线程数是否大于核心线程池的最大数?
如果是,那么销毁当前线程,如果不是,则保留当前线程。
因此当创建的线程数大于核心线程池的最大数,在所有任务执行完毕后,线程池最终线程数量会回到corePoolSize。
二、jdk提供的线程池API:Executor框架:将任务提交与任务执行分离开的机制(解耦)
- Executor接口:基础
execute(Runnable) : void
- ExecutorService接口:继承Executor接口,提供了线程池生命周期管理的方法。
- AbstractExecutorService方法:实现ExecutorService,提供默认实现。
- ScheduledExecutorService接口:继承ExecutorService接口。
- ThreadPoolExecutor方法:最常用的线程池。
- ScheduledThreadPoolExecutor:继承ThreadPoolExecutor,实现ScheduledExecutorService:延后与定期执行。
还涉及到其他的类如下:
三、Executor接口:
就一个void execute(Runnable command)
方法,代表提交一个任务
我们经常这样启动一个线程
new Thread(new Runnable(){
//do something
}).start()
用了线程池Executor就可以这样使用
Executor executor = anExecutor;
executor.execute(new RunnableTask1());
如果我们希望线程池同步执行每一个任务可以这样实现这个接口
class DirectExecutor implements Executor{
public void executor(Runnable r){
r.run();//没有new Thread().start(),也就没有启动任何一个线程,也就是同步的
}
}
如果希望每个任务提交后,直接启动一个新的线程来执行这个任务,可以这样实现
class ThreadPerTaskExecutor implements Executor{
public void execute(Runnable r){
new Thread(r).start();//每个任务一个线程
}
}
组合两个 Executor 来使用,下面这个实现是将所有的任务都加到一个 queue 中,然后从 queue 中取任务,交给真正的执行器执行,这里采用 synchronized 进行并发控制:
class SerialExecutor implements Executor {
// 任务队列
final Queue<Runnable> tasks = new ArrayDeque<Runnable>();
// 这个才是真正的执行器
final Executor executor;
// 当前正在执行的任务
Runnable active;
// 初始化的时候,指定执行器
SerialExecutor(Executor executor) {
this.executor = executor;
}
// 添加任务到线程池: 将任务添加到任务队列,scheduleNext 触发执行器去任务队列取任务
public synchronized void execute(final Runnable r) {
tasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
scheduleNext();
}
}
});
if (active == null) {
scheduleNext();
}
}
protected synchronized void scheduleNext() {
if ((active = tasks.poll()) != null) {
// 具体的执行转给真正的执行器 executor
executor.execute(active);
}
}
}
Executor接口只有提价任务的功能,接下来ExecutorService提供了比较丰富的功能。
四、ExecutorService接口:
一般定义一个线程池,往往使用这个接口,提供了比较丰富的功能。
ExecutorService executor = Executors.newFixedThreadPool();
方法:
public interface ExecutorService extends Executor {
// 关闭线程池,已提交的任务继续执行,不接受继续提交新任务
void shutdown();
// 关闭线程池,尝试停止正在执行的所有任务,不接受继续提交新任务
// 它和前面的方法相比,加了一个单词“now”,区别在于它会去停止当前正在进行的任务
List<Runnable> shutdownNow();
// 线程池是否已关闭
boolean isShutdown();
// 如果调用了 shutdown() 或 shutdownNow() 方法后,所有任务结束了,那么返回true
// 这个方法必须在调用shutdown或shutdownNow方法之后调用才会返回true
boolean isTerminated();
// 等待所有任务完成,并设置超时时间
// 我们这么理解,实际应用中是,先调用 shutdown 或 shutdownNow,
// 然后再调这个方法等待所有的线程真正地完成,返回值意味着有没有超时
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;
// 提交一个 Callable 任务
<T> Future<T> submit(Callable<T> task);
// 提交一个 Runnable 任务,第二个参数将会放到 Future 中,作为返回值,
// 因为 Runnable 的 run 方法本身并不返回任何东西
<T> Future<T> submit(Runnable task, T result);
// 提交一个 Runnable 任务
Future<?> submit(Runnable task);
// 执行所有任务,返回 Future 类型的一个 list
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
// 也是执行所有任务,但是这里设置了超时时间
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException;
// 只有其中的一个任务结束了,就可以返回,返回执行完的那个任务的结果
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException;
// 同上一个方法,只有其中的一个任务结束了,就可以返回,返回执行完的那个任务的结果,
// 不过这个带超时,超过指定的时间,抛出 TimeoutException 异常
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
在继续往下层介绍 ExecutorService 的实现类之前,我们先来说说相关的类 FutureTask。
FutureTask
Future --> RunnableFuture --> FutureTask
Runnable --> RunnableFuture
FutureTask通过Runnable间接实现了Runnable接口,
所以每个Runnable通常都先包装成FutureTask,
然后调用
executor.execute(Runnable command)
将其提交给线程池
我们知道Runnable的run方法是没有返回值的,如果需要会在submit中指定第二个参数作为返回值:
Future submit(Runnable task, T result);
//需要获取结果用submit,不需要可以用execute方法
到时候会通过这两个参数将其包装成Callable,Callable的call方法有返回值,同时如果运行出现异常,call方法会抛出异常
public interface Callable{
V call() throws Exception;
}
五、AbstractExecutorService
抽象类派生自ExecutorService接口,实现了invokeAny方法和invokeAll方法
六、ThreadPoolExecutor详解
这个类实现了一个线程池需要的各个方法,它实现了任务提交、线程管理、监控等等方法。
先看一下任务提交的几个方法:
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
提交任务的submit方法中,参数是Runnable/Callable类型,这个参数不是用于new Thread(runnable).start()中的,此处的参数不是用于启动线程的,这里指的是任务,任务要做的事是run方法里面定义的或call方法定义的。
线程池中一些主要的构件:
ps:我们经常会使用Executors这个工具类来快速构造线程池,最终重点也是构造方法
public 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;
}
构造方法:
构造方法可以让我们自定义(扩展)线程池
1. 指定核心线程数量 corePoolSize
2. 指定最大线程数量 maximumPoolSize
3. 允许线程空闲时间 keepAliveTime:空闲线程的保活时间,如果某线程的空闲时间超过这个值没有任务给他去做,他就可以被关闭了。但是如果线程池中的线程数少于核心线程数,那么这些线程不会因为空闲太长时间而被关闭。
4. 时间对象 unit
5. 阻塞队列 workQueue
6. 线程工厂 threadFactory:用于生成线程
7. 任务拒绝策略 handle
除了构造方法中的主要属性,还存在其他重要的属性:
- 内部状态:
变量ctl定义为AtomicInteger,**记录了线程池中的 任务数量 和 线程池的状态 **两个信息。(其中高三位表示线程池状态、低29位表示线程池中的任务数量)
- 线程池状态runState:
- RUNNING:线程池能够接受新任务,以及对新添加的任务进行处理
- SHUTDOWN:不可以接受新任务,但是可以对已添加的任务进行处理
- STOP:不接受新任务,不处理已添加任务,并且会中断正在处理的任务
- TIDYING:所有任务已终止,会变成TIDYING状态,线程池会执行钩子函数terminated(),terminated()在ThreadPoolExecutor是空的,若用户想在线程池变为TIDYING时进行处理,可以通过重载钩子函数terminated()来实现。
- TERMINATED:线程池彻底终止的状态,terminated方法结束后,状态就会变成这个。
- 状态转换过程:
- RUNNING -> SHUTDOWN:当调用了 shutdown() 后,会发生这个状态转换,这也是最重要的
- (RUNNING or SHUTDOWN) -> STOP:当调用 shutdownNow() 后,会发生这个状态转换,这下要清楚 shutDown() 和 shutDownNow() 的区别了
- SHUTDOWN -> TIDYING:当任务队列和线程池都清空后,会由 SHUTDOWN 转换为 TIDYING
- STOP -> TIDYING:当任务队列清空后,发生这个转换
- TIDYING -> TERMINATED:这个前面说了,当 terminated() 方法结束后
另外,还需要看一下内部类Worker
ps:Worker是一个内部类,就是线程池中做任务的线程。任务是Runnable(内部叫task或者command),线程是Worker
Worker类使用了AQS
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
private static final long serialVersionUID = 6138294804551838833L;
// 这个是真正的线程,任务靠你啦
final Thread thread;
// 前面说了,这里的 Runnable 是任务。为什么叫 firstTask?因为在创建线程的时候,如果同时指定了
// 这个线程起来以后需要执行的第一个任务,那么第一个任务就是存放在这里的(线程可不止执行这一个任务)
// 当然了,也可以为 null,这样线程起来了,自己到任务队列(BlockingQueue)中取任务(getTask 方法)就行了
Runnable firstTask;
// 用于存放此线程完全的任务数,注意了,这里用了 volatile,保证可见性
volatile long completedTasks;
// Worker 只有这一个构造方法,传入 firstTask,也可以传 null
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
// 调用 ThreadFactory 来创建一个新的线程
this.thread = getThreadFactory().newThread(this);
}
// 这里调用了外部类的 runWorker 方法
public void run() {
runWorker(this);
}
...// 其他几个方法没什么好看的,就是用 AQS 操作,来获取这个线程的执行权,用了独占锁
}
接下来看最重要的方法:
execute执行方法
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
// 如果线程池中运行的线程数量 < corePoolSize,
// 则创建一个新的线程,并把当前任务 command 作为这个线程的第一个任务 (firstTask),即使其他辅助线程是空闲的。
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
// 到这里说明,要么当前线程数大于等于核心线程数,要么刚刚 addWorker 失败了
// 如果线程池处于 RUNNING 状态,把这个任务添加到任务队列 workQueue 中
if (isRunning(c) && workQueue.offer(command)) {
/* 这里面说的是,如果任务进入了 workQueue,我们是否需要开启新的线程
* 因为线程数在 [0, corePoolSize) 是无条件开启新的线程
* 如果线程数已经大于等于 corePoolSize,那么将任务添加到队列中,然后进到这里
*/
int recheck = ctl.get();
// 如果线程池已不处于 RUNNING 状态,
// 那么移除已经入队的这个任务,并且执行拒绝策略
if (! isRunning(recheck) && remove(command))
reject(command);
// 如果线程池还是 RUNNING 的,并且线程数为 0,那么开启新的线程
// 到这里这块代码的真正意图是:担心任务提交到队列中了,但是线程都关闭了
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 如果 workQueue 队列满了,那么进入到这个分支
// 以 maximumPoolSize 为界创建新的 worker,
// 如果失败,说明当前线程数已经达到 maximumPoolSize,执行拒绝策略
else if (!addWorker(command, false))
reject(command);
}
再总结一遍这些参数的要点:
线程数量要点:
- 如果运行线程的数量少于核心线程数量,则创建新的线程处理请求
- 如果运行线程的数量大于核心线程数量,小于最大线程数量,则当队列满的时候才创建新的线程
- 如果核心线程数量等于最大线程数量,那么将创建固定大小的连接池
- 如果设置了最大线程数量为无穷,那么允许线程池适合任意的并发数量
线程空闲时间要点:
- 当前线程数大于核心线程数,如果空闲时间已经超过了,那该线程会销毁。
排队策略要点:
- 同步移交:不会放到队列中,而是等待线程执行它。如果当前线程没有执行,很可能会新开一个线程执行。
- 无界限策略:如果核心线程都在工作,该线程会放到队列中。所以线程数不会超过核心线程数
- 有界限策略:可以避免资源耗尽,但是一定程度上减低了吞吐量
当线程关闭或者线程数量满了和队列饱和了,就有拒绝任务的情况了:
拒绝任务策略:
final void reject(Runnable command) {
// 执行拒绝策略
handler.rejectedExecution(command, this);
}
此处的handler需要在构造线程池的时候就传入,他是RejectExecutionHandler的实例,其中由四个类可以直接用。
// 只要线程池没有被关闭,那么由提交任务的线程自己来执行这个任务。
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public CallerRunsPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
// 不管怎样,直接抛出 RejectedExecutionException 异常
// 这个是默认的策略,如果我们构造线程池的时候不传相应的 handler 的话,那就会指定使用这个
public static class AbortPolicy implements RejectedExecutionHandler {
public AbortPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
// 不做任何处理,直接忽略掉这个任务
public static class DiscardPolicy implements RejectedExecutionHandler {
public DiscardPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
// 这个相对霸道一点,如果线程池没有被关闭的话,
// 把队列队头的任务(也就是等待了最长时间的)直接扔掉,然后提交这个任务到等待队列中
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
public DiscardOldestPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}
- 直接抛出异常
- 使用调用者的线程来处理
- 直接丢掉这个任务
- 丢掉最老的任务
- 关闭线程池的相关方法
tryTerminate()
当线程池涉及到要移除worker时候都会调用tryTerminate(),该方法主要用于判断线程池中的线程是否已经全部移除了,如果是的话则关闭线程池。
在关闭线程池的过程中,如果线程池处于STOP状态或者处于SHUDOWN状态且阻塞队列为null,则线程池会调用interruptIdleWorkers()方法中断所有线程,注意ONLY_ONE== true,表示仅中断一个线程。
interruptIdleWorkers
onlyOne==true仅终止一个线程,否则终止所有线程。 - 线程池关闭
- 线程池ThreadPoolExecutor提供了shutdown()和shutDownNow()用于关闭线程池。
shutdown():按过去执行已提交任务的顺序发起一个有序的关闭,但是不接受新任务。
shutdownNow() :尝试停止所有的活动执行任务、暂停等待任务的处理,并返回等待执行的任务列表。
七、Executors
Executors工具类的不同方法按照我们的需求创建了不同的线程池,来满足业务需求。其中所有的方法都是static的。
- FixedThreadPool
创建固定长度的线程池,每次提交任务创建一个线程,直到达到线程池的最大数量,线程池的大小不再变化。这个线程池可以创建固定线程数的线程池。特点就是可以重用固定数量线程的线程池。它的构造源码如下:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
- FixedThreadPool的corePoolSize和maxiumPoolSize都被设置为创建FixedThreadPool时指定的参数nThreads。
- 0L则表示当线程池中的线程数量操作核心线程的数量时,多余的线程将被立即停止
- 最后一个参数表示FixedThreadPool使用了无界队列LinkedBlockingQueue作为线程池的工作队列,由于是无界的,当线程池的线程数达到corePoolSize后,新任务将在无界队列中等待,因此线程池的线程数量不会超过corePoolSize,同时maxiumPoolSize也就变成了一个无效的参数,并且运行中的线程池并不会拒绝任务。
- FixedThreadPool运行图如下
- 执行过程如下:
- 如果当前工作中的线程数量少于corePool的数量,就创建新的线程来执行任务。
- 当线程池的工作中的线程数量达到了corePool,则将任务加入LinkedBlockingQueue。
- 线程执行完1中的任务后会从队列中去任务。
注意LinkedBlockingQueue是无界队列,所以可以一直添加新任务到线程池。
- SingleThreadExecutor
SingleThreadExecutor是使用单个worker线程的Executor。特点是使用单个工作线程执行任务。它的构造源码如下:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
SingleThreadExecutor的corePoolSize和maxiumPoolSize都被设置1。
其他参数均与FixedThreadPool相同,其运行图如下:
执行过程如下:
- 如果当前工作中的线程数量少于corePool的数量,就创建一个新的线程来执行任务。
- 当线程池的工作中的线程数量达到了corePool,则将任务加入LinkedBlockingQueue。
- 线程执行完1中的任务后会从队列中去任务。
注意:由于在线程池中只有一个工作线程,所以任务可以按照添加顺序执行。
- CachedThreadPool
CachedThreadPool是一个”无限“容量的线程池,它会根据需要创建新线程。特点是可以根据需要来创建新的线程执行任务,没有特定的corePool。下面是它的构造方法:
CachedThreadPool的corePoolSize被设置为0,即corePool为空;
maximumPoolSize被设置为Integer.MAX_VALUE,即maximum是无界的。
这里keepAliveTime设置为60秒,意味着空闲的线程最多可以等待任务60秒,否则将被回收。
CachedThreadPool使用没有容量的SynchronousQueue作为主线程池的工作队列,它是一个没有容量的阻塞队列。每个插入操作必须等待另一个线程的对应移除操作。这意味着,如果主线程提交任务的速度高于线程池中处理任务的速度时,CachedThreadPool会不断创建新线程。极端情况下,CachedThreadPool会因为创建过多线程而耗尽CPU资源。其运行图如下:
执行过程如下:
- 首先执行SynchronousQueue.offer(Runnable task)。如果在当前的线程池中有空闲的线程正在执行SynchronousQueue.poll(),那么主线程执行的offer操作与空闲线程执行的poll操作配对成功,主线程把任务交给空闲线程执行。,execute()方法执行成功,否则执行步骤2
- 当线程池为空(初始maximumPool为空)或没有空闲线程时,配对失败,将没有线程执行SynchronousQueue.poll操作。这种情况下,线程池会创建一个新的线程执行任务。
- 在创建完新的线程以后,将会执行poll操作。当步骤2的线程执行完成后,将等待60秒,如果此时主线程提交了一个新任务,那么这个空闲线程将执行新任务,否则被回收。因此长时间不提交任务的CachedThreadPool不会占用系统资源。
SynchronousQueue是一个不存储元素阻塞队列,每次要进行offer操作时必须等待poll操作,否则不能继续添加元素。
具体的使用实例
- . newCachedThreadPool :
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。示例代码如下:
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int index = i;
try {
Thread.sleep(index * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//线程池的执行方法
cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(index);
}
});
}
线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。
- . newFixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。示例代码如下:
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
final int index = i;
fixedThreadPool.execute(new Runnable() {
@Override
public void run() {
try {
System.out.println(index);
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
因为线程池大小为3,每个任务输出index后sleep 2秒,所以每两秒打印3个数字。
定长线程池的大小最好根据系统资源进行设置。如Runtime.getRuntime().availableProcessors()。可参考PreloadDataCache。
- newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行。延迟执行示例代码如下:
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
scheduledThreadPool.schedule(new Runnable() {
@Override
public void run() {
System.out.println("delay 3 seconds");
}
}, 3, TimeUnit.SECONDS);
表示延迟3秒进行
定期执行代码如下:
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("delay 1 seconds, and excute every 3 seconds");
}
}, 1, 3, TimeUnit.SECONDS);
表示延迟1秒后每3秒执行一次。
- newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。示例代码如下:
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int index = i;
singleThreadExecutor.execute(new Runnable() {
@Override
public void run() {
try {
System.out.println(index);
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
结果依次输出,相当于顺序执行各个任务。
现行大多数GUI程序都是单线程的。Android中单线程可用于数据库操作,文件操作,应用批量安装,应用批量删除等不适合并发但可能IO阻塞性及影响UI线程响应的操作。
总结
- java 线程池有哪些关键属性?
corePoolSize,maximumPoolSize,workQueue,keepAliveTime,rejectedExecutionHandler
- corePoolSize 到 maximumPoolSize 之间的线程会被回收,当然 corePoolSize 的线程也可以通过设置而得到回收(allowCoreThreadTimeOut(true))。
workQueue 用于存放任务,添加任务的时候,如果当前线程数超过了 corePoolSize,那么往该队列中插入任务,线程池中的线程会负责到队列中拉取任务。
keepAliveTime 用于设置空闲时间,如果线程数超出了 corePoolSize,并且有些线程的空闲时间超过了这个值,会执行关闭这些线程的操作
rejectedExecutionHandler 用于处理当线程池不能执行此任务时的情况,默认有抛出 RejectedExecutionException 异常、忽略任务、使用提交任务的线程来执行此任务和将队列中等待最久的任务删除,然后提交此任务这四种策略,默认为抛出异常。 - 说说线程池中的线程创建时机?
- 如果当前线程数少于 corePoolSize,那么提交任务的时候创建一个新的线程,并由这个线程执行这个任务;
- 如果当前线程数已经达到 corePoolSize,那么将提交的任务添加到队列中,等待线程池中的线程去队列中取任务;
- 如果队列已满,那么创建新的线程来执行任务,需要保证池中的线程数不会超过 maximumPoolSize,如果此时线程数超过了 maximumPoolSize,那么执行拒绝策略。
- 任务执行过程中发生异常怎么处理?
如果某个任务执行出现异常,那么执行任务的线程会被关闭,而不是继续接收其他任务。然后会启动一个新的线程来代替它。 - 什么时候会执行拒绝策略?
- workers 的数量达到了 corePoolSize(任务此时需要进入任务队列),任务入队成功,与此同时线程池被关闭了,而且关闭线程池并没有将这个任务出队,那么执行拒绝策略。这里说的是非常边界的问题,入队和关闭线程池并发执行,读者仔细看看 execute 方法是怎么进到第一个 reject(command) 里面的。
- workers 的数量大于等于 corePoolSize,将任务加入到任务队列,可是队列满了,任务入队失败,那么准备开启新的线程,可是线程数已经达到 maximumPoolSize,那么执行拒绝策略。