1、简介
Executor
接口。该接口提供了一种将任务提交与每个任务的运行机制分离的方法,包括线程使用、调度等细节。通常使用执行器,而不是显式创建线程。
例如:创建线程 new Thread(new(RunnableTask())).start()
更改:
Executor executor = anExecutor;
executor.execute(new RunnableTask1());
executor.execute(new RunnableTask2());
Executors
线程池工具类,相当于一个工厂类,用来创建合适的线程池,返回ExecutorService(继承Executor)类型的线程池
ExecutorService newFixedThreadPool() : 创建固定大小的线程池
ExecutorService newSingleThreadExecutor() : 创建单个线程池。 线程池中只有一个线程
ExecutorService newCachedThreadPool() : 缓存线程池,线程池的数量不固定,可以根据需求自动的更改数量。
ScheduledExecutorService newScheduledThreadPool() : 创建固定大小的线程,可以延迟或定时的执行任务
ScheduledExecutorService newSingleThreadScheduledExecutor() : 创建单个可以延迟或定时的执行任务线程池
ExecutorService
ExecutorService为线程池接口,提供了线程池生命周期方法,继承自Executor接口。
Shutdown方法将允许在终止之前执行以前提交的任务,而Shutdownow方法将阻止等待的任务启动,并尝试停止当前正在执行的任务。终止后,执行者没有正在执行的任务,没有等待执行的任务,也不能提交新任务。应关闭未使用的ExecutorService,以便回收其资源。
方法invokeAny和invokeAll执行最常用的批量执行形式,执行一组任务,然后等待至少一个或全部任务完成。
ThreadPoolExecutor
ExecutorService的实现类,是线程池的真正实现,他通过构造方法的一系列参数,来构成不同配置的线程池。
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:核心线程数,默认情况下核心线程会一直存活,即使处于闲置状态也不会受keepAliveTime限制。除非将allowCoreThreadTimeOut设置为true。
maximumPoolSize:线程池所能容纳的最大线程数。超过这个数的线程将被阻塞。当任务队列为没有设置大小的LinkedBlockingDeque时,这个值无效。
keepAliveTime:非核心线程的闲置超时时间,超过这个时间就会被回收。
unit:指定keepAliveTime的单位,如TimeUnit.SECONDS。当将allowCoreThreadTimeOut设置为true时对corePoolSize生效。
workQueue:线程池中的任务队列.常用的有三种队列,SynchronousQueue,LinkedBlockingQueue,ArrayBlockingQueue。
threadFactory:线程工厂,提供创建新线程的功能。ThreadFactory是一个接口,只有一个方法interface()
handler:策略,RejectedExecutionHandler也是一个接口,只有一个方法rejectedExecution()
当线程池中的资源已经全部使用,添加新线程被拒绝时,会调用RejectedExecutionHandler的rejectedExecution方法。
四种可选策略
# AbortPolicy(中止策略):直接抛出java.util.concurrent.RejectedExecutionException异常
# CallerRunsPolicy(呼叫方运行策略):若已达到待处理队列长度,将由主线程直接处理请求
# DiscardOldestPolicy(放弃最古老的政策):抛弃旧的任务;会导致被丢弃的任务无法再次被执行
# DiscardPolicy(放弃政策):抛弃当前任务;会导致被丢弃的任务无法再次被执行
线程池、new Thread
a. 每次new Thread新建对象性能差。
b. 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机。
c. 缺乏更多功能,如定时执行、定期执行、线程中断。
相比new Thread,线程池的好处在于:
a. 重用存在的线程,减少对象创建、消亡的开销,性能佳。
b. 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
c. 提供定时执行、定期执行、单线程、并发数控制等功能。
2、线程的类型
固定线程池
Executors.newFixedThreadPool
/**
* 创建一个线程池,该线程池重用在共享无边界队列上运行的固定数量的线程,在需要时使用提供的ThreadFactory创建新线程。
* 在任何时候,线程最多都是活动的处理任务。如果在所有线程都处于活动状态时提交其他任务,它们将在队列中等待,直到有线程可用。如果任何线程在关机前的执行过程中由于故障而终止,那么如果需要执行后续任务,将有一个新线程替代它。
* 池中的线程将一直存在,直到显式关闭。
*/
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads,
nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
//defaultHandler:new AbortPolicy()
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
public static void main(String[] args) {
fixedThreadPool();
}
public static void fixedThreadPool() {
// 创建固定大小的线程池,线程数3
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3, new ThreadFactory() {
int i = 0;
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "fixed-" + i++);
}
});
for (int i = 0; i < 10; i++) {
fixedThreadPool.execute(() -> {
try {
Thread.sleep(100);
log.info("固定线程池");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
单一线程池
Executors.newSingleThreadExecutor
/**
* 创建一个执行器,该执行器使用一个工作线程在无界队列中运行,并在需要时使用提供的ThreadFactory创建一个新线程。
* 与其他等效的newFixedThreadPool(1,threadFactory)不同,返回的执行器保证不可重新配置以使用其他线程
*/
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
public static void singleThreadExecutor() {
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() {
int i = 0;
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "single-" + i++);
}
});
for (int i = 0; i < 10; i++) {
singleThreadExecutor.execute(() -> {
try {
Thread.sleep(100);
log.info("单一线程池");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
缓存线程池
Executors.newCachedThreadPool
/**
* 创建一个线程池,该线程池根据需要创建新线程,但在以前构建的线程可用时将重用这些线程,并在需要时使用提供的ThreadFactory创建新线程。
*/
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
public static void cachedThreadPool() throws InterruptedException {
ExecutorService cachedThreadPool = Executors.newCachedThreadPool(new ThreadFactory() {
int i = 0;
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "cached-" + i++);
}
});
for (int i = 0; i < 10; i++) {
cachedThreadPool.execute(() -> {
try {
Thread.sleep(100);
log.info("缓存线程池");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
Thread.sleep(5000);
for (int i = 0; i < 20; i++) {
cachedThreadPool.execute(() -> {
try {
Thread.sleep(100);
log.info("缓存线程池");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
周期性线程池
Executors.newScheduledThreadPool
/**
* 创建一个线程池,该线程池可以安排命令在给定延迟后运行,或定期执行
*/
public static ScheduledExecutorService newScheduledThreadPool(
int corePoolSize, ThreadFactory threadFactory) {
return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
public ScheduledThreadPoolExecutor(int corePoolSize,ThreadFactory threadFactory) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), threadFactory);
}
//ScheduledThreadPoolExecutor的父类是ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
public static void scheduledThreadPool() {
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5, new ThreadFactory() {
int i = 0;
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "scheduled-" + i++);
}
});
log.info("周期线程池,调用前");
scheduledThreadPoolA(scheduledThreadPool);
// scheduledThreadPoolB(scheduledThreadPool);
// scheduledThreadPoolC(scheduledThreadPool);
}
private static void scheduledThreadPoolA(ScheduledExecutorService scheduledThreadPool) {
for (int i = 0; i < 10; i++) {
scheduledThreadPool.schedule(() -> {
try {
Thread.sleep(100);
log.info("周期线程池,调用时延迟5秒执行一次任务,只执行一次");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, 5, TimeUnit.SECONDS);
}
}
private static void scheduledThreadPoolB(ScheduledExecutorService scheduledThreadPool) {
for (int i = 0; i < 10; i++) {
scheduledThreadPool.scheduleAtFixedRate(() -> {
try {
Thread.sleep(1000);
log.info("周期线程池,调用时延迟5秒执行一次任务,之后立即计算每过10秒再次执行一次,会不停重复执行,不受任务耗时影响,时间间隔固定");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, 5, 10, TimeUnit.SECONDS);
}
}
private static void scheduledThreadPoolC(ScheduledExecutorService scheduledThreadPool) {
for (int i = 0; i < 10; i++) {
scheduledThreadPool.scheduleWithFixedDelay(() -> {
try {
Thread.sleep(1000);
log.info("周期线程池,调用时延迟5秒执行一次任务,任务执行完毕之后才开始计算第10秒再次执行一次,会不停重复执行,受任务耗时影响,时间间隔不固定");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, 5, 10, TimeUnit.SECONDS);
}
}
单一周期线程池
Executors.newSingleThreadScheduledExecutor
/**
* 创建一个单线程执行器,该执行器可以安排命令在给定延迟后运行,或定期执行。
* 但是请注意,如果此单线程在关机前的执行过程中由于故障而终止,那么如果需要执行后续任务,将使用一个新线程代替它。
* 任务保证按顺序执行,并且在任何给定时间都不会有多个任务处于活动状态。
* 与其他等效的newScheduledThreadPool(1,threadFactory)不同,返回的执行器保证不可重新配置以使用其他线程。
*/
public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1, threadFactory));
}
public ScheduledThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), threadFactory);
}
public static void singleThreadScheduledExecutor() {
ScheduledExecutorService singleThreadScheduledExecutor = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
int i = 0;
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "single-sch-" + i++);
}
});
log.info("单一周期线程池,调用前");
for (int i = 0; i < 10; i++) {
singleThreadScheduledExecutor.schedule(() -> {
try {
Thread.sleep(1000);
log.info("单一周期线程池,调用时延迟5秒执行一次任务,只执行一次");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, 5, TimeUnit.SECONDS);
}
}
3、工作队列
线程池的规则
如果当前运行的线程数小于corePoolSize,有空闲的核心线程,则用空闲的线程来执行任务,没有就创建新线程(核心线程)来执行任务。
如果当前运行的线程数大于或等于corePoolSize,那么就把任务加入等待队列。
如果等待队列已满,那么创建新线程(非核心线程)来执行该任务。
如果创建新线程时导致当前运行的线程数超过maximumPoolSize,就根据饱和策略来拒绝该任务
SynchronousQueue队列
private static final AtomicInteger ai = new AtomicInteger(1);
private static final AtomicInteger ai2 = new AtomicInteger(1);
public static void WorkQueue() throws InterruptedException {
threadPoolExecutorA();
// threadPoolExecutorB();
// threadPoolExecutorC();
}
/**
* 核心3,最大6,超时10秒,SynchronousQueue队列无长度
* 当前运行线程数<=核心数,直接使用核心数
* 当前运行线程数>核心数,不会加入任务队列,而是创建非核心线程来执行任务
* 当前运行线程数>最大数,进行饱和策略(目前策略是直接丢弃)
* 空闲非核心数,超过空闲时间时移除
*/
public static void threadPoolExecutorA() throws InterruptedException {
ThreadPoolExecutor threadPoolExecutorA = new ThreadPoolExecutor(
3,
6,
10, TimeUnit.SECONDS,
new SynchronousQueue<>(),
new ThreadFactory() {
int i = 0;
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "threadA-" + i++);
}
},
new ThreadPoolExecutor.DiscardPolicy());
System.out.println("先开三个线程");
start(threadPoolExecutorA);
System.out.println("=========================================");
System.out.println("再开三个线程");
start(threadPoolExecutorA);
System.out.println("=========================================");
System.out.println("再开三个线程");
start(threadPoolExecutorA);
System.out.println("=========================================");
System.out.println("20秒后再开三个线程");
Thread.sleep(20000);
start(threadPoolExecutorA);
}
private static void start(ThreadPoolExecutor threadPoolExecutor) {
for (int i = 1; i <= 3; i++) {
log.info("创建线程总数:" + ai.getAndIncrement());
threadPoolExecutor.execute(() -> {
try {
Thread.sleep(3000);
log.info("执行线程数:" + ai2.getAndIncrement());
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
System.out.println("核心线程数:" + threadPoolExecutor.getCorePoolSize());
System.out.println("线程池数:" + threadPoolExecutor.getPoolSize());
System.out.println("队列任务数:" + threadPoolExecutor.getQueue().size());
}
ArrayBlockingQueue队列
/**
* 核心3,最大6,超时10秒,ArrayBlockingQueue队列长度5
* 当前运行线程数<=核心数,直接使用核心数
* 当前运行线程数>核心数,加入任务队列
* 当前运行线程数>任务队列长度,创建非核心数(核心+非核心<=最大数)
* 当前运行线程数>最大数,进行饱和策略(目前策略是直接丢弃)
* 空闲非核心数,超过空闲时间时移除
*/
public static void threadPoolExecutorB() throws InterruptedException {
ThreadPoolExecutor threadPoolExecutorB = new ThreadPoolExecutor(
3,
6,
10, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(5),
new ThreadFactory() {
int i = 0;
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "threadB-" + i++);
}
},
new ThreadPoolExecutor.DiscardPolicy());
System.out.println("先开三个线程");
start(threadPoolExecutorB);
System.out.println("=========================================");
System.out.println("再开三个线程");
start(threadPoolExecutorB);
System.out.println("=========================================");
System.out.println("再开三个线程");
start(threadPoolExecutorB);
System.out.println("=========================================");
System.out.println("20秒后再开三个线程");
Thread.sleep(20000);
start(threadPoolExecutorB);
}
LinkedBlockingDeque队列
/**
* 核心3,最大6,超时10秒,LinkedBlockingDeque队列长度5
* 当前运行线程数<=核心数,直接使用核心数
* 当前运行线程数>核心数,加入任务队列
* 当前运行线程数>任务队列长度,创建非核心数(核心+非核心<=最大数)
* 当前运行线程数>最大数,进行饱和策略(目前策略是直接丢弃)
* 空闲非核心数,超过空闲时间时移除
*/
public static void threadPoolExecutorC() throws InterruptedException {
ThreadPoolExecutor threadPoolExecutorC = new ThreadPoolExecutor(
3,
6,
10, TimeUnit.SECONDS,
new LinkedBlockingDeque<>(5),
new ThreadFactory() {
int i = 0;
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "threadC-" + i++);
}
},
new ThreadPoolExecutor.DiscardPolicy());
System.out.println("先开三个线程");
start(threadPoolExecutorC);
System.out.println("=========================================");
System.out.println("再开三个线程");
start(threadPoolExecutorC);
System.out.println("=========================================");
System.out.println("再开三个线程");
start(threadPoolExecutorC);
System.out.println("=========================================");
System.out.println("20秒后再开三个线程");
Thread.sleep(20000);
start(threadPoolExecutorC);
}
链表队列的执行效果跟数组队列是一样的
队列比较
队列 | 是否有界 | 是否缓冲 | 锁个数 | 并发性能 |
---|---|---|---|---|
SynchronousQueue(同步队列) | 无 | 无 | 1 | 线程少 (<20) ,Queue长度短 (<30) , 使用SynchronousQueue表现很稳定,而且在20个线程之内不管Queue长短,SynchronousQueue性能表现是最好的, SynchronousQueue跟Queue长短没有关系) |
ArrayBlockingQueue(数组阻塞队列) | 有 | 有 | 1 | 一般不用,并且在插入和删除元素时会产生额外的对象(跟LinkedBlockingQueue区别是必须指定大小) |
LinkedBlockingQueue(链接阻塞队列) | 无 | 有 | 2(生产者锁和消费者锁) | LinkedBlockingQueue性能表现远超ArrayBlockingQueue,不管线程多少,不管Queue长短,LinkedBlockingQueue都胜过ArrayBlockingQueue,线程多(>20),Queue长度长(>30),使用LinkedBlockingQueue(当LinkedBlockingQueue没指定大小是,最大线程数无效) |
4、ThreadPoolTaskExecutor
ThreadPoolExecutor是一个java类不提供spring生命周期和参数装配
ThreadPoolTaskExecutor实现了InitializingBean, DisposableBean ,xxaware等,具有spring特性。
相比较于ThreadPoolExecutor:
1、ThreadPoolTaskExecutor底层使用ThreadPoolExecutor构造线程池并进行增强,扩展了更多特性
2、ThreadPoolTaskExecutor只关注自己增强的部分,任务执行还是ThreadPoolExecutor处理。
3、ThreadPoolTaskExecutor使用@Bean注入,容器关闭会自动调用其实现InitializingBean接口的关闭方法。
/**
* 配置:ThreadPoolTaskExecutor
*/
@Bean(name = "threadPoolTaskExecutor", destroyMethod = "shutdown")
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 设置线程池前缀:方便排查
executor.setThreadNamePrefix("thread-task-");
// 设置线程池的核心线程数量
executor.setCorePoolSize(10);
// 设置线程池的最大值
executor.setMaxPoolSize(15);
// 设置线程池的队列大小
executor.setQueueCapacity(250);
// 设置线程最大空闲时间,单位:秒(非核心线程数)
executor.setKeepAliveSeconds(3000);
// 饱和策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
// 实现了InitializingBean接口,可不需要执行这一初始化步骤
// executor.initialize();
return executor;
}
@Slf4j
@Component
@EnableScheduling
public class ExecutorTestService {
@Scheduled(fixedRate = 60 * 60 * 1000)
public void fixedRate() throws Exception {
threadPoolTaskExecutor();
}
@Resource(name = "threadPoolTaskExecutor")
private ThreadPoolTaskExecutor taskExecutor;
public void threadPoolTaskExecutor() {
for (int i = 0; i < 20; i++) {
taskExecutor.execute(() -> {
try {
Thread.sleep(2000);
log.info("ThreadPoolTaskExecutor 的 execute 方法调用");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
}
5、ScheduledThreadPoolExecutor
一个可以额外安排命令在给定延迟后运行,或定期执行的ThreadPoolExecutor。
/**
* 配置:ScheduledThreadPoolExecutor
*/
@Bean(name = "scheduledPoolTaskExecutor", destroyMethod = "shutdown")
public ScheduledThreadPoolExecutor scheduledThreadPoolExecutor() {
// 第一个参数:核心线程数
// 第二个参数:线程工厂(此处用来设置线程名)
// 第三次参数:饱和策略
// 饱和策略
return new ScheduledThreadPoolExecutor(10,
new ThreadFactory() {
int i = 0;
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "scheduled-" + i++);
}
},
new ThreadPoolExecutor.DiscardPolicy()
);
}
@Resource(name = "scheduledPoolTaskExecutor")
private ScheduledThreadPoolExecutor scheduled;
public void scheduledPoolTaskExecutor() throws Exception {
scheduleA();
}
public void scheduleA() throws ExecutionException, InterruptedException {
scheduled.execute(new Runnable() {
@Override
public void run() {
log.info("execute ");
}
});
Thread.sleep(2000);
scheduled.submit(new Runnable() {
@Override
public void run() {
log.info("submit 无返回值");
}
});
Thread.sleep(2000);
Future<String> result = scheduled.submit(new Runnable() {
@Override
public void run() {
log.info("submit 有返回值");
}
}, "ok");
System.out.println("result = " + result.get());
Thread.sleep(2000);
Future<String> submit = scheduled.submit(new Callable<String>() {
@Override
public String call() throws Exception {
log.info("submit 有返回值");
return "ok";
}
});
System.out.println("submit = " + submit.get());
}
public void scheduleB() throws ExecutionException, InterruptedException {
scheduled.schedule(new Runnable() {
@Override
public void run() {
log.info("方法调用,延迟5秒执行一次, 只执行一次");
}
}, 5000, TimeUnit.MILLISECONDS);
ScheduledFuture<String> result = scheduled.schedule(new Callable<String>() {
@Override
public String call() {
log.info("方法调用,延迟10秒执行一次, 只执行一次");
return "ok";
}
}, 10000, TimeUnit.MILLISECONDS);
System.out.println("result = " + result.get());
}
public void scheduleC() {
scheduled.scheduleAtFixedRate(new Runnable() {
@SneakyThrows
@Override
public void run() {
log.info("方法调用,延迟5秒首次执行,之后每过3秒轮询, 不受run方法执行耗时影响");
Thread.sleep(2000);
}
}, 5000, 3000, TimeUnit.MILLISECONDS);
}
public void scheduleD() {
scheduled.scheduleWithFixedDelay(new Runnable() {
@SneakyThrows
@Override
public void run() {
log.info("方法调用,延迟5秒首次执行,之后每过3秒轮询,受run方法执行耗时影响");
Thread.sleep(2000);
}
}, 5000, 3000, TimeUnit.MILLISECONDS);
}