文章目录
前言
多线程技术
是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。
在java中,开启一个线程,我们一般通过集成Thread类和实现Runnnable接口调用其start方法启动,如果一直使用这种方式去执行线程的创建、执行、结束,这是串行的处理方式,而多线程是多个事件在同一时间启动多个线程执行,也就是并行的方式,这样达到多路同时运行,充分利用CPU的性能。
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
System.out.println("-------execute--------");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
for (int i = 0; i < 10; i++) {
new Thread(runnable).start();
}
以上是一段多线程的代码,循环创建多个线程,执行后的CPU/线程数比例如下:
注:单机四核八线程
执行结果可以看出,随着线程数增多,CPU的占用量就会上升,如果高并发场景的每个线程都是执行很短的时间便结束了,那样频繁的创建线程和销毁进程会大大的降低系统运行的效率。
当然并不是所有场景下都要使用多线程技术实现,在一些并发不高,要求同步结果,注重结果交互的业务中如管理系统,同步API服务等直接串行执行就可,多线程存在着对象共享、线程安全、运行及结果不确定因素,需要对线程内部做好异常处理和主线程的数据交互。
单线程串行调用我们就需要解决效率低的问题,使线程可以复用,不用频繁的系统开销,线程池就登场了。
线程池
线程池的作用
- 减少资源消耗,通过重复的使用已创建好的线程,避免了线程的频繁创建和销毁所造成的消耗
- 提高响应速度,当任务到达的时候,不需要再去创建,可以直接使用已经创建好的线程就能立即执行任务
- 提高线程的管理性,线程池可以统一管理线程的创建,销毁,分配,调优监控
说明:由于线程池涉及到很多复杂的派生类和实现逻辑,诸如FutureTask,ForkJoinPool,线程安全,部分ExecutorService实现类等都是一个独立的知识点模块,可能会涉及到,但不摊开所有讲了,此文我们主要对常用的主要线程池的线性剖析,起到一个抛砖引玉的效果就好,其中的使用和配置都需要各位根据真实现场环境去调试,等后续有机会逐一对每一个知识点重点关注。
深入线程池
jdk1.5后加入concurrent包,Executor是线程池的顶级接口,只声明的一个执行操作,具体的接口要交给它的子类:ExecutorService
* @since 1.5
* @author Doug Lea
*/
public interface Executor {
/**
* Executes the given command at some time in the future. The command
* may execute in a new thread, in a pooled thread, or in the calling
* thread, at the discretion of the {@code Executor} implementation.
*
* @param command the runnable task
* @throws RejectedExecutionException if this task cannot be
* accepted for execution
* @throws NullPointerException if command is null
*/
void execute(Runnable command);
}
既然要使用ExecutorService去实现线程池,那怎么去创建ExecutorService呢?并且ExecutorService有不同的具体实现,都需要如何去实例化?那接下来介绍下Executors
Executors
是一个工厂类,用于实例化ExecutorService、ScheduledExecutorService、ThreadFactory、Callable,它可以实例化这么多异步任务家族的配置,这货简直就是个造物主。我们拎出来几个常用的方法来研究下:
- newFixedThreadPool:创建固定大小的线程池,每次提交一个任务就创建一个线程,直到线程达到线程池的最大nThreads容量,多余的放入无界队列中(队列大小Integer.MAX_VALUE,太大了直接无边界),如果运行中的一个线程出现异常,则从队列中候补一个执行,直到全部执行完成或者shutdown命令,nThreads不允许<=0,扩展:也可以指定ThreadFactory创建。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
- newSingleThreadExecutor:创建一个单线程的线程池,这个池子里正在工作的线程只能有一个,相当于串行任务,多余的放入无界队列中,如果当前任务异常,则从队列中取一个执行,且是顺序执行,FIFO(先入先出),这跟newFixedThreadPool(1)是一个效果,扩展:也可以指定ThreadFactory创建。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
- newCachedThreadPool:创建一个可缓存的线程池,可以理解为一个线程是有空闲超时时间的,当一个线程执行完后会有60s的时间供以下一个线程复用,如果到期没有使用则丢弃,这是个无穷大的线程池,且是无序执行。假设有100个线程,每个线程需要执行2秒,那会创建100个线程,等第101个线程进来时,如果2秒后thread1执行完了,那不用创建新线程,接着使用thread1线程执行第101条数据,扩展:也可以指定ThreadFactory创建。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
- newScheduledThreadPool:创建指定核心线程数的无穷大线程池,这个池子适用于周期性或者延迟任务,内部与前面不同,使用ScheduledThreadPoolExecutor特有线程池实现,任务会放入DelayedWorkQueue队列中,此队列基于堆实现的优先级,可以充分利用数据内存空间,延时时间越短地就排在队列的前面,内部又是基于BlockingQueue实现,所以又是线程安全的。后面会讲到ScheduledExecutorService,到时候看下具体实现。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
ExecutorService
是真正执行线程池的接口,其中提供一些方法用于管理服务终止以返回计算结果(Future) 的执行任务的方法。Future是用于跟踪异步处理的返回结果,查看任务是否完成,取消未完成的任务等等。
/**
* 开始关停 ExecutorService。
* 关停前提交的任务会继续执行,但是不会接受新任务。如果是已经关停的 ExecutorService 调用该方法不会发生任何变换。
*
* @throws SecurityException 如果存在安全管理器并且关闭线程没有权限修改线程状态,那么会抛出该异常。没有权限修改的原因是没有获得 RuntimePermission 或者是安全管理器检查访问权限时校验未通过。
*/
void shutdown();
/**
* 立即关停 ExecutorService
* 尝试关停所有进行中的任务,同时拒绝任何新任务,停止处理等待中的任务,并且返回等待任务的列表。
* 正在进行中的任务没法保证一定成功停止,比如通过线程中断方法(Thread.interrupt)中断进行中的任务,而任务没有响应线程中断,那么任务无法终止。
*
* @return 正在进行中的任务列表
* @throws SecurityException 如果存在安全管理器并且关闭线程没有权限修改线程状态,那么会抛出该异常。没有权限修改的原因是没有获得 RuntimePermission 或者是安全管理器检查访问权限时校验未通过。
*/
List<Runnable> shutdownNow();
/**
* 返回关闭状态:如果关闭则返回 true,否则返回 false
*
* @return 关闭状态:如果关闭则返回 true,否则返回 false
*/
boolean isShutdown();
/**
* 如果关闭后所有任务都完成那么返回 true
*
* @return 如果关闭后所有任务都完成那么返回 true
*/
boolean isTerminated();
/**
* 当关闭 ExecutorService 之后用于阻塞等待给定的时间,让正在进行中的任务完成,直到任意条件满足:
* 1. 所有任务都已经完成
* 2. 等待超时
* 3. 线程被中断
*
* @param 等待的时间
* @param unit the time unit of the timeout argument 时间单位
* @return 如果 ExecutorService 在时间内终止了那么返回 true,否则返回 false
* @throws InterruptedException 当前线程被中断抛出中断异常
*/
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;
/**
* 提交一个可执行的并且有返回值的任务,返回一个任务执行结果(Future)。
* 如果希望立即阻塞直到获取异步任务结果可以使用 result = exec.submit(aCallable).get()。Future.get()或阻塞直到获取到异步任务结果。
* ExecutorService 提供了很多方法用于将闭包对象转成被执行的方法。
*
* @param 可被 ExecutorService 执行的任务
* @param <T> 任务的执行结束后返回的结果的对象类型
* @return 可以跟踪异步任务执行结果的对象(Future)
* @throws RejectedExecutionException 如果 ExecutorService 拒绝任务则抛出
* @throws NullPointerException if the task is null
*/
<T> Future<T> submit(Callable<T> task);
/**
* 提交一个可执行的任务,并指定任务返回的对象类型,该方法返回一个可以跟踪任务执行结果(Future)
* @param task 可执行的任务
* @param Result 该任务执行后返回的类型
* @param <T>任务的执行结束后返回的结果的对象类型
* @return 任务执行结果(Future)
* @throws RejectedExecutionException ExecutorService 拒绝任务后抛出该异常
* @throws NullPointerException 如果任务(task)是 null 抛出该异常
*/
<T> Future<T> submit(Runnable task, T result);
/**
* 提交一个可执行的任务,并返回一个可以跟踪任务执行结果的对象(Future)
*
* @param task 可执行的任务
* @return 任务执行结果(Future)
* @throws RejectedExecutionException 如果 ExecutorService 拒绝该任务则抛出该异常
* @throws NullPointerException 如果提交了一个空任务则抛出该异常
*/
Future<?> submit(Runnable task);
/**
* 执行批量任务并返回跟踪这批任务执行结果的对象集合(List<Future>)
* 该方法会阻塞等待任务结束后才将结果返回,所以调用方拿到结果时,任务已经结束(或者任务执行过程中断导致的异常结束)。
* 如果在 invokeAll 执行期间改变任务集合可能会引发未定义方法说明上(throws 定义的异常列表)的异常。
* 比如 invokeAll 使用 for 循环遍历集合时集合被删除元素抛出 ConcurrentModificationException 异常。
*
* @param tasks 任务集合
* @param <T> 任务类型
* @return 任务执行结果(Future)集合
* @throws InterruptedException 如果在等待结果过程中线程被中断则抛出该异常
* @throws NullPointerException 如果任务结合是空对象则抛出该异常
* @throws RejectedExecutionException 如果任务提交被拒绝则抛出该异常
*/
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
/**
* 在上一个 invokeAll 基础上增加了等待时间。
* 如果超时,批量任务终止并抛出异常
*
* @param tasks 任务集合
* @param timeout 等待时间
* @param unit 时间单位
* @param <T> 任务完成后返回类型
* @throws InterruptedException 如果在等待过程中线程被中断则抛出该异常
* @throws NullPointerException 如果提交的任务集合是空对象则抛出该异常
* @throws RejectedExecutionException 如果本次提交被拒绝则抛出该异常
*/
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException;
/**
* 执行给定任务列表中的任一任务。
* 如果有任一任务执行完成则将该任务执行结果返回,并且取消其他任务。
*
* @param tasks 任务集合
* @param <T> 任务返回的类型
* @return 任务执行结果(Future)
* @throws InterruptedException 如果等待过程中线程被中断则抛出中断异常
* @throws NullPointerException 如果提交的任务
* @throws IllegalArgumentException 如果任务是空列表则抛出该异常
* @throws ExecutionException 如果没有任何任务执行成功则抛出该异常
* @throws RejectedExecutionException 如果任务被拒绝则抛出该异常
*/
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException;
/**
* 在上一个 invokeAny 基础上增加等待时间
*
* @param tasks 任务集合
* @param timeout 等待时间
* @param unit 时间单位
* @param <T> 任务返回的类型
* @return 任务执行结果(Future)
* @throws InterruptedException 如果等待过程中线程被中断则抛出中断异常
* @throws NullPointerException 如果提交的任务
* @throws TimeoutException 如果任务是空列表则抛出该异常
* @throws ExecutionException 如果没有任何任务执行成功则抛出该异常
* @throws RejectedExecutionException 如果任务被拒绝则抛出该异常
*/
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
涉及到几个重要的类
类、接口 | 说明 |
---|---|
AbstractExecutorService | ExecutorService的默认实现,并且补充了对RunnableFuture的支持 |
ScheduledExecutorService | 提供周期性、定时、延迟任务的执行 |
ThreadPoolExecutor | AbstractExecutorService的一个派生类,提供默认线程池的生成工具以及更多相关配置 |
ScheduledThreadPoolExecutor | 继承ThreadPoolExecutor 和ScheduledExecutorService 的实现,周期性任务调度的类实现 |
AbstractExecutorService
实现了 ExecutorService 定义的执行任务的方法,其中在jdk1.6后加入了newTaskFor方法,用于构建 RunnableFuture 对象。
执行任务方法返回的跟踪任务执行结果的对象都是通过 newTaskFor 来构建的,可以通过自定义 newTaskFor 来构建所需的 RunnableFuture。
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
RunnableFuture是Runnable和Future的综合体,你会发现为什么会有run方法,这是因为Runnable中的run方式是抽象的,在其派生类需要对抽象方法实现,根据源码中,不论submit还是invokeAll其实都是基于RunnableFuture进行的执行
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> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException {
...
ArrayList<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
boolean done = false;
try {
for (Callable<T> t : tasks) {
RunnableFuture<T> f = newTaskFor(t);
futures.add(f);
execute(f);
}
}
...
}
ScheduledExecutorService
先看一个例子:
public static void schedule() {
Runnable runnable = () -> log.info("-------execute--------");
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
log.info("-------begin--------");
for (int i = 0; i < 3; i++) {
scheduledExecutorService.schedule(runnable, 3, TimeUnit.SECONDS);
}
}
执行结果:
15:24:51,728 --main-- [com.jikeh.test.ThreadTest] -------begin--------
15:24:54,741 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] -------execute--------
15:24:54,741 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] -------execute--------
15:24:54,741 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] -------execute--------
从结果可以看出,scheduledExecutorService.schedule是提供延迟任务执行的,到了设置的delay值,会自动全部执行。其内部原理是将delay数据与当前时间换算得到一个未来执行的时间值,利用ScheduledFutureTask创建一个一次性的带时间的任务。
再来一个例子:
public static void scheduleWithDelay() {
Runnable runnable = () -> log.info("-------execute--------");
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
log.info("-------begin--------");
scheduledExecutorService.scheduleWithFixedDelay(runnable, 3, 2, TimeUnit.SECONDS);
}
执行结果:
15:43:00,650 --main-- [com.jikeh.test.ThreadTest] -------begin--------
15:43:03,663 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] -------execute--------
15:43:05,675 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] -------execute--------
15:43:07,680 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] -------execute--------
15:43:09,687 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] -------execute--------
15:43:11,692 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] -------execute--------
15:43:13,700 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] -------execute--------
15:43:15,709 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] -------execute--------
...
从结果可以看出,该任务是从第三秒开始执行,每隔两秒执行一次,无止尽的执行下去,使用了scheduledExecutorService.scheduleWithFixedDelay会以第三个参数delay值为周期的循环执行,其原理跟schedule方法很像,只不过是在ScheduledFutureTask创建时是以给定的周期值执行任务。
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.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;
}
以上是建立一个ThreadPoolExecutor,其中参数一一解释
corePoolSize:核心线程数,决定了一个新的线程是执行还是放入workQueue队列等待,只要指定数量就会开辟固定的线程池数量,哪怕是空闲的,所以该参数要结合实际情况去设定,避免空间浪费。
maximumPoolSize:最大线程数,可自定义大小,结合workQueue队列的类型和大小来触发使用,如果占满该数量时,其运行最大线程数包含corePoolSize数,并不是简单的corePoolSize+maximumPoolSize数,设置合理大小,可以充分利用CPU性能处理,开满火力,全速执行。
keepAliveTime:存活时间,当线程池中空闲线程数量超过corePoolSize时,多余的线程会在多长时间内被销毁,可参照cache线程池的设置和使用理解。
unit:keepAliveTime的时间单位
workQueue:任务队列,一般可分为同步队列,有界队列,无界队列,优先级队列。
threadFactory:可以自定义线程属性交由ThreadFactory生成newThread,一般选择默认或者没有此参数的构造方法。
handler:拒绝策略,当maximumPoolSize和workQueue都占满了,触发该策略,根据配置的策略执行不用的拒绝方式,根据实际业务设置。
线程池中线程执行流转图:
接下来就对其重点参数进一步的分析:
workQueue
- 同步队列:SynchronousQueue,也可称为直接执行队列,没有任何缓存概念,只要放入线程就立刻执行,是一个轻量级的线程队列,其特点就是默认无序执行,也可自定义有序,阻塞,直接。如果有比较大的工作队列容量需求,又想避免无界队列带来的问题,配合合适的拒绝策略,可以考虑无缓存空间的SynchronousQueue
public static void synchronousQueue() {
Runnable runnable = () -> log.info("-------execute--------");
SynchronousQueue<Runnable> runnables = new SynchronousQueue<>();
ExecutorService executorService = new ThreadPoolExecutor(1, 2, 0, TimeUnit.SECONDS, runnables,
new ThreadPoolExecutor.AbortPolicy());
log.info("-------begin--------");
for (int i = 0; i < 3; i++) {
executorService.execute(runnable);
}
}
执行结果:
由于配置的拒绝策略是AbortPolicy,所以超过最大容量直接报错,这也说明了,SynchronousQueue是同时直接运行,如果在第三个线程执行前Thread.sleep(1000)一下,则可正常执行三次。这种情况,需要对程序的并发量有个准确的评估,才能设置合适的maximumPoolSize数量,否则很容易就会执行拒绝策略。
- 有界队列:ArrayBlockingQueue,数据结构是以数组形式,构造方法默认要传大小,明确限定了线程池的等待数量。若有新的任务需要执行时,线程池会创建新的线程,直到创建的线程数量达到corePoolSize时,则会将新的任务加入到等待队列中。若等待队列已满,即超过ArrayBlockingQueue初始化的容量,则继续创建线程,直到线程数量达到maximumPoolSize设置的最大线程数量,若大于maximumPoolSize,则执行拒绝策略。线程的数量和有界队列大小有直接关系,如果设置的容量过大,则一直维持corePoolSize数量执行,如果过小,则很容易触发拒绝策略。
public static void arrayQueue() {
Runnable runnable = () -> log.info("-------execute--------");
ArrayBlockingQueue<Runnable> arrayBlockingQueue = new ArrayBlockingQueue<>(10);
ExecutorService executorService = new ThreadPoolExecutor(1, 2, 0, TimeUnit.SECONDS, arrayBlockingQueue,
new ThreadPoolExecutor.AbortPolicy());
log.info("-------begin--------");
for (int i = 0; i < 12; i++) {
executorService.execute(runnable);
}
}
如果循环次数改为13,就会报错,因为超过了最大线程容量+最大队列容量,所以使用该队列,需要评估业务线程数的大致数量。
- 无界队列:LinkedBlockingQueue,其实无界是相对的,因为该队列里默认最大容量是Integer.MAX_VALUE,我们不可能达到这个量级的队列,就算达到,我们的硬件也不支持,所以可以理解为无界,当然也可以指定大小容量,如果用该队列默认数据,会发现线程池永远都是corePoolSize的容量去执行任务,永远不可能触发maximumPoolSize大小,因为全部被LinkedBlockingQueue吸收了,当然也不会触发拒绝策略了,这就要考验你硬件水平的时候了。
/**
* Creates a {@code LinkedBlockingQueue} with a capacity of
* {@link Integer#MAX_VALUE}.
*/
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
/**
* Creates a {@code LinkedBlockingQueue} with the given (fixed) capacity.
*
* @param capacity the capacity of this queue
* @throws IllegalArgumentException if {@code capacity} is not greater
* than zero
*/
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
last = head = new Node<E>(null);
}
- 优先级队列:PriorityBlockingQueue,线程需要实现Comparable接口,是因为需要对数据进行排序对比,是一个特殊的无界队列,正常无界队列是按照FIFO顺序执行的,而优先级队列是按照特定要求的顺序排列。
public static void priorityQueue() {
class MyQueue implements Runnable, Comparable<MyQueue> {
private int priority;
public MyQueue() {
}
public MyQueue(int priority) {
this.priority = priority;
}
@Override
public int compareTo(@NotNull MyQueue o) {
// 当前线程优先级与传入优先级比较,值最小优先级最高,此处实现正序佩列
return o.priority > this.priority ? -1 : 1;
}
@Override
public void run() {
log.info("=====priority:{}=====", priority);
}
}
PriorityBlockingQueue<Runnable> priorityBlockingQueue = new PriorityBlockingQueue();
ExecutorService executorService = new ThreadPoolExecutor(1, 2, 0, TimeUnit.SECONDS, priorityBlockingQueue,
new ThreadPoolExecutor.AbortPolicy());
log.info("-------begin--------");
for (int i = 0; i < 12; i++) {
executorService.execute(new MyQueue(i));
}
}
执行结果:等于0的priority直接执行输出,剩下的经过了优先级队列的重新排列整合
12:52:28,755 --main-- [com.jikeh.test.ThreadTest] -------begin--------
12:52:28,761 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] =====priority:0=====
12:52:28,763 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] =====priority:1=====
12:52:28,764 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] =====priority:2=====
12:52:28,764 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] =====priority:3=====
12:52:28,764 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] =====priority:4=====
12:52:28,764 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] =====priority:5=====
12:52:28,764 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] =====priority:6=====
12:52:28,764 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] =====priority:7=====
12:52:28,764 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] =====priority:8=====
12:52:28,764 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] =====priority:9=====
12:52:28,764 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] =====priority:10=====
12:52:28,764 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] =====priority:11=====
拒绝策略
当线程池的最大线程数和队列都满载运行时,会触发拒绝策略进一步对线程池做一些限制措施,避免资源耗尽,共有五种策略,我们通过源码来分析下
- AbortPolicy 顾名思义,该线程拒绝执行最终流产,没有正常执行,这就相当于你跟人表白,对方拒绝你还给你了一巴掌,但是你得记录这一巴掌的后果,别以后找不到是谁赏你的,这还是线程池的默认拒绝策略,多残忍,从源码可以看出直接throw异常
/**
* A handler for rejected tasks that throws a
* {@code RejectedExecutionException}.
*/
public static class AbortPolicy implements RejectedExecutionHandler {
/**
* Creates an {@code AbortPolicy}.
*/
public AbortPolicy() { }
/**
* Always throws RejectedExecutionException.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
* @throws RejectedExecutionException always
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
- CallerRunsPolicy 调用者执行策略,当线程池满载运行,然后再让调用者执行,如果主线程结束了,则就丢失了需要在主线程执行的线程。
public static class CallerRunsPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code CallerRunsPolicy}.
*/
public CallerRunsPolicy() { }
/**
* Executes task r in the caller's thread, unless the executor
* has been shut down, in which case the task is discarded.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
示例:
public static void arrayQueue() {
Runnable runnable = () -> log.info("-------execute--------");
ArrayBlockingQueue<Runnable> arrayBlockingQueue = new ArrayBlockingQueue<>(5);
ExecutorService executorService = new ThreadPoolExecutor(1, 2, 0, TimeUnit.SECONDS, arrayBlockingQueue,
new ThreadPoolExecutor.CallerRunsPolicy());
log.info("-------begin--------");
for (int i = 0; i < 8; i++) {
executorService.execute(runnable);
}
log.info("-------end--------");
}
执行结果:
14:11:37,028 --main-- [com.jikeh.test.ThreadTest] -------begin--------
14:11:37,032 --main-- [com.jikeh.test.ThreadTest] -------execute--------
14:11:37,032 --main-- [com.jikeh.test.ThreadTest] -------end--------
14:11:37,032 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] -------execute--------
14:11:37,032 --pool-1-thread-2-- [com.jikeh.test.ThreadTest] -------execute--------
14:11:37,033 --pool-1-thread-2-- [com.jikeh.test.ThreadTest] -------execute--------
14:11:37,033 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] -------execute--------
14:11:37,033 --pool-1-thread-2-- [com.jikeh.test.ThreadTest] -------execute--------
14:11:37,033 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] -------execute--------
14:11:37,033 --pool-1-thread-2-- [com.jikeh.test.ThreadTest] -------execute--------
- DiscardOldestPolicy 丢弃老线程策略,典型的喜新厌旧渣渣,为了执行新的任务,就丢失老的任务
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code DiscardOldestPolicy} for the given executor.
*/
public DiscardOldestPolicy() { }
/**
* Obtains and ignores the next task that the executor
* would otherwise execute, if one is immediately available,
* and then retries execution of task r, unless the executor
* is shut down, in which case task r is instead discarded.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
// 重点在这里使用轮询去找队列中最后的线程执行,相当于取尾去头的执行
// 跟这个相反的有个peek(),不会删除头部元素
e.getQueue().poll();
e.execute(r);
}
}
}
示例:
public static void arrayQueue() {
Runnable runnable = () -> log.info("-------execute--------");
class MyThread implements Runnable {
private int param;
public MyThread(int param) {
this.param = param;
}
@Override
public void run() {
log.info("====={}=====", param);
}
}
ArrayBlockingQueue<Runnable> arrayBlockingQueue = new ArrayBlockingQueue<>(5);
ExecutorService executorService = new ThreadPoolExecutor(1, 2, 0, TimeUnit.SECONDS, arrayBlockingQueue,
new ThreadPoolExecutor.DiscardOldestPolicy());
log.info("-------begin--------");
for (int i = 0; i < 10; i++) {
executorService.execute(new MyThread(i));
}
log.info("-------end--------");
}
执行结果:
14:23:06,369 --main-- [com.jikeh.test.ThreadTest] -------begin--------
14:23:06,374 --main-- [com.jikeh.test.ThreadTest] -------end--------
14:23:06,374 --pool-1-thread-2-- [com.jikeh.test.ThreadTest] =====6=====
14:23:06,374 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] =====0=====
14:23:06,376 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] =====5=====
14:23:06,376 --pool-1-thread-2-- [com.jikeh.test.ThreadTest] =====4=====
14:23:06,376 --pool-1-thread-2-- [com.jikeh.test.ThreadTest] =====8=====
14:23:06,376 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] =====7=====
14:23:06,376 --pool-1-thread-2-- [com.jikeh.test.ThreadTest] =====9=====
- DiscardPolicy 典型的拒绝策略,这是真拒绝啊,只要线程池满载执行,这货就拒绝执行其余任务了,很干脆,先到先得,后来的别想进去。
/**
* A handler for rejected tasks that silently discards the
* rejected task.
*/
public static class DiscardPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code DiscardPolicy}.
*/
public DiscardPolicy() { }
/**
* Does nothing, which has the effect of discarding task r.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
// 这里是空空如也,啥也不干
}
}
- 自定义策略 我们可以实现RejectedExecutionHandler接口,达到我们自定义拒绝策略的要求
public static void arrayQueue() {
Runnable runnable = () -> log.info("-------execute--------");
class MyThread implements Runnable {
private int param;
public MyThread(int param) {
this.param = param;
}
@Override
public void run() {
log.info("====={}=====", param);
}
}
ArrayBlockingQueue<Runnable> arrayBlockingQueue = new ArrayBlockingQueue<>(5);
class MyPolicy implements RejectedExecutionHandler {
public MyPolicy() {}
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
log.info("My policy, I call the shots");
}
}
ExecutorService executorService = new ThreadPoolExecutor(1, 2, 0, TimeUnit.SECONDS, arrayBlockingQueue,
new MyPolicy());
log.info("-------begin--------");
for (int i = 0; i < 8; i++) {
executorService.execute(new MyThread(i));
}
log.info("-------end--------");
}
执行结果:
14:40:07,573 --main-- [com.jikeh.test.ThreadTest] -------begin--------
14:40:07,578 --main-- [com.jikeh.test.ThreadTest] My policy, I call the shots
14:40:07,579 --main-- [com.jikeh.test.ThreadTest] -------end--------
14:40:07,579 --pool-1-thread-2-- [com.jikeh.test.ThreadTest] =====6=====
14:40:07,579 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] =====0=====
14:40:07,581 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] =====2=====
14:40:07,581 --pool-1-thread-2-- [com.jikeh.test.ThreadTest] =====1=====
14:40:07,581 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] =====3=====
14:40:07,581 --pool-1-thread-2-- [com.jikeh.test.ThreadTest] =====4=====
14:40:07,581 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] =====5=====
ThreadFactory
上文提到过在新建线程池时可以扩展使用ThreadFactory构建传入线程,在ThreadFactory中我们可以自定义
public static void synchronousQueue() {
Runnable runnable = new Runnable() {
@Override
public void run() {
log.info("-------execute----{}----", Thread.currentThread().getName());
}
};
class MyThread implements ThreadFactory {
public MyThread() {}
@Override
public Thread newThread(@NotNull Runnable r) {
Thread thread = new Thread(r);
thread.setName("MyThread-"+r.hashCode());
return thread;
}
}
SynchronousQueue<Runnable> runnables = new SynchronousQueue<>();
ExecutorService executorService = new ThreadPoolExecutor(1, 2, 0, TimeUnit.SECONDS, runnables, new MyThread(),
new ThreadPoolExecutor.AbortPolicy());
log.info("-------begin--------");
for (int i = 0; i < 2; i++) {
executorService.execute(runnable);
}
}
执行结果:
--main-- [com.jikeh.test.ThreadTest] -------begin--------
--MyThread-1712669532-- [com.jikeh.test.ThreadTest] -------execute----MyThread-1712669532----
--MyThread-423031029-- [com.jikeh.test.ThreadTest] -------execute----MyThread-423031029----
ThreadPoolExecutor的扩展钩子服务
- beforeExecute:线程池中任务运行前执行
- afterExecute:线程池中任务运行完毕后执行
- terminated:线程池退出后执行
这三个服务可以很好的监控每个线程的执行过程,提供相关的日志或者业务功能
public static void myThread() {
class MyThread implements Runnable {
private int flag;
public MyThread(int flag) {
this.flag = flag;
}
@Override
public void run() {
log.info("-------execute---{}-----", flag);
}
}
ExecutorService executorService = new ThreadPoolExecutor(1, 2, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5),
new ThreadPoolExecutor.CallerRunsPolicy()) {
protected void beforeExecute(Thread t, Runnable r) {
log.info("---执行前动作:{}", ((MyThread) r).flag);
}
protected void afterExecute(Runnable r, Throwable t) {
log.info("---执行后动作:{}", ((MyThread) r).flag);
}
protected void terminated() {
log.info("---当前线程池退出---");
}
};
log.info("-------begin--------");
for (int i = 0; i < 8; i++) {
executorService.execute(new MyThread(i));
}
executorService.shutdown();
}
执行结果:
15:25:24,092 --main-- [com.jikeh.test.ThreadTest] -------begin--------
15:25:24,097 --main-- [com.jikeh.test.ThreadTest] -------execute---7-----
15:25:24,097 --pool-1-thread-2-- [com.jikeh.test.ThreadTest] ---执行前动作:6
15:25:24,097 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] ---执行前动作:0
15:25:24,099 --pool-1-thread-2-- [com.jikeh.test.ThreadTest] -------execute---6-----
15:25:24,099 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] -------execute---0-----
15:25:24,099 --pool-1-thread-2-- [com.jikeh.test.ThreadTest] ---执行后动作:6
15:25:24,099 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] ---执行后动作:0
15:25:24,099 --pool-1-thread-2-- [com.jikeh.test.ThreadTest] ---执行前动作:1
15:25:24,099 --pool-1-thread-2-- [com.jikeh.test.ThreadTest] -------execute---1-----
15:25:24,099 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] ---执行前动作:2
15:25:24,099 --pool-1-thread-2-- [com.jikeh.test.ThreadTest] ---执行后动作:1
15:25:24,099 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] -------execute---2-----
15:25:24,099 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] ---执行后动作:2
15:25:24,099 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] ---执行前动作:3
15:25:24,100 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] -------execute---3-----
15:25:24,100 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] ---执行后动作:3
15:25:24,100 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] ---执行前动作:4
15:25:24,100 --pool-1-thread-2-- [com.jikeh.test.ThreadTest] ---执行前动作:5
15:25:24,100 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] -------execute---4-----
15:25:24,100 --pool-1-thread-2-- [com.jikeh.test.ThreadTest] -------execute---5-----
15:25:24,100 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] ---执行后动作:4
15:25:24,100 --pool-1-thread-2-- [com.jikeh.test.ThreadTest] ---执行后动作:5
15:25:24,100 --pool-1-thread-2-- [com.jikeh.test.ThreadTest] ---当前线程池退出---
如何配置线程池中的容量大小
《java并发编程实践》书中第8章第141页有这样一个公式:
Nthreads = Ncpu x Ucpu x (1 + W/C)
Ncpu = CPU的数量 (可通过获取:Runtime.getRuntime().availableProcessors();)
Ucpu = 目标CPU的使用率, 0 <= Ucpu <= 1
W/C = 等待时间与计算时间的比率
大致可用两个方向去分析:IO密集型和计算密集型
IO密集型:一般情况下,只要存在IO操作,那一定会有耗时,也就是W/C>1,我们还要考虑CPU的利用率,内存空间,线程数等等 ,不过由于TPS我们不可能一直维持在一定的峰值,所以我们可以大致设定为1,这样我们在配置时,核心线程数=CPU数,最大线程数=2*CPU数。
计算密集型:这个也叫CPU型,主要计算操作对CPU的消耗,计算时间会比等待时间大很多,而目前基于CPU计算性能的提升,大不多说情况下,等待时间都趋近于0,0<=W/C<1,所以我们在配置时要充分利用CPU性能,核心线程数=CPU数,最大线程数=CPU数+1。
以上是大多数情况下的一种正常配置,并没有一个明确的指标,根据实际情况,只要不是设置的偏大和偏小就可以。
而设置线程队列数大小可以通过以下方式计算:
最优线程数 =(线程等待时间/线程执行时间 + 1)x Ncpu
假设执行一个线程所需要的时间是0.3s,等待时间是1.2s,CPU是4线程的话,那可以计算(1.2/0.3 + 1)x 4 = 20 个线程,如果再排除核心线程数的执行,那队列里可以设置16个线程等待,如果要发挥最大线程数执行,那差不多在10个左右就可,不过一般不会设置的过于紧凑,所以设置20个就可以。
以上计算提供一个思路,这样可以避免随意设置线程数的问题,生产设置需要根据实际运行时间和TPS去调整。
ScheduledThreadPoolExecutor
是一个周期性和任务延迟执行的线程池,延迟任务提交到线程池后开始执行,但具体何时执行则不知道,延迟任务是根据先进先出(FIFO)的提交顺序来执行的。
内部维护一个ScheduledFutureTask类,来控制线程的时间和周期执行,当然也可以设置成非延迟执行,即 time=0
private class ScheduledFutureTask<V>
extends FutureTask<V> implements RunnableScheduledFuture<V> {
/**
* Sequence number to break ties FIFO
* FIFO队列的序列号
*/
private final long sequenceNumber;
/**
* The time the task is enabled to execute in nanoTime units
* 以毫秒为单位的相对于任务创建时刻的等待时间,即延迟时间
*/
private long time;
/**
* Period in nanoseconds for repeating tasks. A positive
* value indicates fixed-rate execution. A negative value
* indicates fixed-delay execution. A value of 0 indicates a
* non-repeating task.
* 以纳秒为单位的周期时间,正数表明fixed-rate执行,负数表明delay-rate执行,0表明非重复
*/
private final long period;
/**
* The actual task to be re-enqueued by reExecutePeriodic
* 当前任务
*/
RunnableScheduledFuture<V> outerTask = this;
/**
* Index into delay queue, to support faster cancellation.
* 进入延迟队列的索引值,它便于取消任务
*/
int heapIndex;
...
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
ScheduledThreadPoolExecutor的构造方法只支持设置核心线程数,是个无界队列,所以虽然继承了ThreadPoolExecutor,但是ThreadPoolExecutor的一些调优手段对ScheduledThreadPoolExecutor效果甚微,不过支持自定义的ThreadFactory和拒绝策略。
我们看到存活时间设置为0,说明就没有缓存时间,任何线程都不是闲着没事干的,由于ScheduledThreadPoolExecutor会将一个线程设置为leader,用于等待队列的根节点直到获取并运行任务,而这个leader又会随时去唤醒别的线程,所以除了核心线程,其余的都处于阻塞状态,等待领导人召唤。
long delay = first.getDelay(NANOSECONDS);
if(delay <= 0)
return finishPoll(first);
first = null; // don't retain ref while waiting
if (leader != null)
available.await();
else {
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
available.awaitNanos(delay);
} finally {
if (leader == thisThread)
leader = null;
}
}
这段源码说明了leader是如何产生的,按照每个线程的延迟时间判断,如果还没到执行时间,则看当前的leader执行过了没,如果不存在了则将当前线程设置为leader,否则等待。
我们发现其使用的队列是DelayedWorkQueue,这个队列是基于堆结构实现的优先级队列,而堆结构又是基于满二叉树数据结构实现,数组的初始容量是16,由于二叉树的特性,会不断的扩容增长,所以这也就说明为什么ScheduledThreadPoolExecutor的最大线程数要设置为无穷大了。
private static final int INITIAL_CAPACITY = 16;
private RunnableScheduledFuture<?>[] queue =
new RunnableScheduledFuture<?>[INITIAL_CAPACITY];
private final ReentrantLock lock = new ReentrantLock();
private int size = 0;
private Thread leader = null;
schedule
这个方法是ScheduledThreadPoolExecutor的核心执行所在,重写的execute和submit方法实则都是执行的schedule方法
public void execute(Runnable command) {
schedule(command, 0, NANOSECONDS);
}
public Future<?> submit(Runnable task) {
return schedule(task, 0, NANOSECONDS);
}
而schedule的执行就运用到了ScheduleFutureTask,通过triggerTime去转换需要执行的时间,最后交由delayExecute执行
public ScheduledFuture<?> schedule(Runnable command,
long delay,
TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
RunnableScheduledFuture<?> t = decorateTask(command,
new ScheduledFutureTask<Void>(command, null,
triggerTime(delay, unit)));
delayedExecute(t);
return t;
}
long triggerTime(long delay) {
return now() +
((delay < (Long.MAX_VALUE >> 1)) ? delay : overflowFree(delay));
}
delayedExecute的执行是等待leader线程消费任务
private void delayedExecute(RunnableScheduledFuture<?> task) {
// 线程池状态为shutdown,则执行拒绝策略拒绝任务
if (isShutdown())
reject(task);
else {
// 将任务放入优先队列中
super.getQueue().add(task);
// shutdown状态下,非周期任务不会移除队列
if (isShutdown() &&
!canRunInCurrentRunState(task.isPeriodic()) &&
remove(task))
// shutdown状态下,周期任务会默认移除队列
task.cancel(false);
else
// 如果池内线程数小于核心线程数,则新建一个线程
ensurePrestart();
}
}
void ensurePrestart() {
int wc = workerCountOf(ctl.get());
if (wc < corePoolSize)
addWorker(null, true);
else if (wc == 0)
addWorker(null, false);
}
scheduledAtFixedRate
是周期性的任务,其中分别设置延迟时间和周期时间,那么问题来了,如果执行的时间要大于周期会怎么样?还记得DelayedWorkQueue中的一个参数ReentrantLock吗?当一个线程执行过程中会对当前线程加锁,线程执行完会释放锁,如果锁没释放,其他线程别想执行。
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
if (period <= 0)
throw new IllegalArgumentException();
ScheduledFutureTask<Void> sft =
new ScheduledFutureTask<Void>(command,
null,
triggerTime(initialDelay, unit),
unit.toNanos(period));
RunnableScheduledFuture<Void> t = decorateTask(command, sft);
sft.outerTask = t;
delayedExecute(t);
return t;
}
示例:
public static void scheduledFixed() {
Runnable runnable = () -> {
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("-------execute--------");
};
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1);
log.info("-------begin--------");
executorService.scheduleAtFixedRate(runnable, 0, 5, TimeUnit.SECONDS);
}
执行结果:
11:00:23,285 --main-- [com.jikeh.test.ThreadTest] -------begin--------
11:00:29,291 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] -------execute--------
11:00:35,298 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] -------execute--------
11:00:41,306 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] -------execute--------
11:00:47,315 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] -------execute--------
11:00:53,320 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] -------execute--------
从结果可以看出,主线程启动后,线程池中的子线程在6秒以后开始执行,由于每个线程所需的时间是6s >设置的period = 5s,所以每个线程执行开始时间是每个线程执行的时间后执行,反之如果线程执行时间 < period 则时间周期就是period 。
scheduleWithFixedDelay
是在执行完一个线程之后,会在给定的delay时间后再次执行
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
if (delay <= 0)
throw new IllegalArgumentException();
ScheduledFutureTask<Void> sft =
new ScheduledFutureTask<Void>(command,
null,
triggerTime(initialDelay, unit),
unit.toNanos(-delay));
RunnableScheduledFuture<Void> t = decorateTask(command, sft);
sft.outerTask = t;
delayedExecute(t);
return t;
}
示例:
public static void scheduledWithFixed() {
Runnable runnable = () -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("-------execute--------");
};
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1);
log.info("-------begin--------");
executorService.scheduleWithFixedDelay(runnable, 0, 3, TimeUnit.SECONDS);
}
执行结果:
10:57:08,642 --main-- [com.jikeh.test.ThreadTest] -------begin--------
10:57:10,654 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] -------execute--------
10:57:15,668 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] -------execute--------
10:57:20,683 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] -------execute--------
10:57:25,691 --pool-1-thread-1-- [com.jikeh.test.ThreadTest] -------execute--------
从结果可以看出,每个线程执行时间是2s,除第一个正常执行后,后续每个线程的开始执行时间=每个线程所需时间+延迟时间delay
private void setNextRunTime() {
long p = period;
if (p > 0)
time += p;
else
time = triggerTime(-p);
}
上面源码,结合scheduleAtFixedRate和scheduleWithFixedDelay的构造方法,可以得出两者的区别:
scheduleAtFixedRate的period大于0,走if逻辑,下一个线程调度时间 = 上一次的执行时间 + 设置周期。如果执行时间小于周期,那下一个线程的调度时间=0+周期,可以理解为按时执行;如果执行时间大于周期,因为每个线程的调度时间已经被周期预分配好了,这样势必导致下一个线程的调度时间要小于下一个线程本来应该的执行时间,所以当上一个执行完后,下一个线程会立刻执行,设置的周期相当于无用了,后面的线程依次如此。
scheduleWithFixedDelay的delay小于0,走else逻辑,now()+delay,下一个线程调度时间 = 当前时间(上一个线程执行完成时间)+ 设置延迟时间。不论上一个线程执行多久,下一个线程都会在完成时间基础上加上延迟时间。
由于两者的Runnable任务都是延迟任务,因此任务都会加入到优先队列中等候。
总体来说ScheduledThreadPoolExecutor的使用场景是特定的情况下,相对来说并不是很常见,只是线程池的多态运用,支持定时周期的执行,所以各位根据实际情况酌情使用以上几种线程池。