不管是在
Java
中还是在Android
中使用到的线程池都是一样的,那就是Executor
线程池框架。
# Executor 框架是什么?
Executor
框架是实现线程池的功能,我们知道线程池就是线程的集合,在线程池中来管理线程,以实现线程的重用性,降低资源消耗,提高响应速度,线程用于执行异步任务,单个线程既是工作单元也是执行机制,从JDK 1.5
开始为了把工作单元与执行机制分离开,然后就诞生了Executor
框架,它是一个用于统一创建与运行的接口。
Executor
框架是Java
并发编程中的一个重要组成部分,它提供了一种标准的方式来执行任务,它包含Executor
、ExecutorService
、Callable
、Future
等接口和类,可以有效地管理线程的生命周期、执行任务以及获取任务的执行结果。
我们还需要明白另一个问题,为什么需要线程池?在
Java
中使用线程来执行异步任务时,线程的创建和销毁需要一定的开销,如果我们为每一个任务创建一个新线程来执行的话,那么这些线程的创建与销毁将消耗大量的计算资源,同时为每一个任务创建一个新线程来执行,这样的方式可能会使处于高负荷状态的应用产生崩溃。
此时线程池的出现将为解决这个问题带来了曙光,我们可以在线程池中创建若干条线程,当有任务需要执行时就从该线程池中获取一条线程来执行任务,如果一时间的任务过多并超出了线程池中的数量,那么后面的线程任务将进入一个等待队列进行等待,直到线程池中有线程处于空闲时才从等待队列获取要执行的任务进行处理,以此循环,这样就大大减少了线程创建与销毁的开销,当然也会缓解应用处于超负荷时的情况。
# Executor 两级调度模型
在
Java
线程启动时会创建一个本地操作系统线程,当该Java
线程终止时,这个操作系统线程也将会被回收,而每一个Java
线程都会被一对一映射为本地操作系统的线程,操作系统会调度所有的线程并将它们分给可用的CPU
。
所谓的映射方式的实现,在上层
Java
多线程程序通过把应用分为若干个任务,然后使用用户级的调度器(Executor框架)
将这些任务映射为固定数量的线程,在底层操作系统内核将这些线程映射到硬件处理器上,两级调度模型示意图如下:
从上图中可以看出应用程序通过
Executor
框架控制上层的调度,而下层的调度由操作系统内核来控制,下层的调度不受应用程序的控制。
# Executor 框架结构
Executor
框架结构主要包括三个部分:- 任务:包括被执行任务需要实现的接口
Runnable
或Callable
接口。 - 任务的执行:包括任务执行机制的核心接口
Executor
以及继承自Executor
的ExecutorService
接口,Executor
有两个关键类实现了ExecutorService
接口,它们分别是ThreadPoolExecutor
和ScheduledThreadPoolExecutor
类。 - 异步计算的结果:包括接口
Future
和实现Future
接口的FutureTask
类。
- 任务:包括被执行任务需要实现的接口
- 下面是通过
UML
类图展示这些类之间的关系,关系图如下:
Extecutor
是一个接口,它是Executor
框架的基础,它将任务的提交与任务的执行分离开来。ThreadPoolExecutor
是线程池的核心实现类,它用来执行被提交的任务。ScheduledThreadPoolExecutor
是一个实现类,它可以在给定的延迟后运行命令或定期执行命令,它比Timer
更灵活,功能更强大。Future
接口和实现Future
接口的FutureTask
类代表异步计算的结果。Runnable
接口和Callable
接口的实现类都可以被ThreadPoolExecutor
或ScheduleThreadPoolExecutor
类执行,区别就是Runnable
无法返回执行结果,而Callable
可以返回执行结果。- 执行关系图如下:
在上图中主线程首先创建实现
Runnable
或Callable
接口的任务对象,工具类Executors
可以把一个Runnable
对象封装成一个Callable
对象,可以使用如下两种方式:
Executors.callable(Runnable task); | |
Executors.callable(Runnable task,Object resule); |
也可以把
Runnable
对象直接提交给ExecutorService
来执行,方法如下:
ExecutorService.execute(Runnable command); |
或者也可以把
Runnable
对象或Callable
对象提交给ExecutorService
执行,可以使用如下两种方式:
ExecutorService.submit(Runnable task); | |
ExecutorService.submit(Callable<T> task); |
注意如果要执行
ExecutorService.submit(Runnable task)
或ExecutorService.submit(Callable<T> task)
方法,那么ExecutorService
将返回一个实现Future
接口的对象 (也就是FutureTask
)。
当然由于
FutureTask
实现了Runnable
接口,也可以直接创建FutureTask
类然后提交给ExecutorService
来执行。此时Executor
框架的主要结构体系就介绍完了,下面将是两个主要的线程池实现类的解析。
# ThreadPoolExecutor
ThreadPoolExecutor
是线程的真正实现,通常使用工厂类Executor
来创建,但它的构造方法提供了一系列参数来配置线程池,下面是ThreadPoolExecutor
构造方法中的各个参数表示的含义。
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory){ | |
this(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,threadFactory,defaultHandler); | |
} |
corePoolSize
:线程池的核心线程数,在默认情况下,核心线程数会一直在线程池中存活,即使它们处于闲置状态。- 如果将
ThreadPoolExecutor
的allowCoreThreadTimeOut
属性设置为true
,那么闲置的核心线程在等待新任务到来时会执行超时策略,这个时间间隔由keepAliveTime
所指定,当等待时间超过keepAliveTime
所指定的时长后,核心线程将被终止。
- 如果将
maximumPoolSize
:线程池所能容纳的最大线程数量,当活动线程数到达这个数值后,后续的新任务将会被阻塞。keepAliveTime
:非核心线程闲置时的超时时长,超过这个时长,非核心线程就会被回收。- 当
ThreadPoolExecutor
的allowCoreThreadTimeOut
属性设置为true
时,keepAliveTime
同样会作用于核心线程。
- 当
unit
:用于指定keepAliveTime
参数的时间单位,它是一个枚举类型,常用的有TimeUnit.MILLISECONDS(毫秒)
、TimeUnit.SECONDS(秒)
以及TimeUnit.MINUTES(分钟)
等。workQueue
:线程池中的任务队列,通过线程池的execute
方法提交Runnable
对象会存储在这个队列中。threadFactory
:线程工厂,为线程池提供创建新线程的功能。ThreadFactory
是一个接口,它只有一个newThread(Runnable r)
方法。- 还有一个不常用的参数那就是
RejectExecutionHandler
,它表示当ThreadPoolExecutor
已经关闭或ThreadPoolExecutor
已经达到了最大线程池大小而且工作队列已满,execute
方法将会调用Handler
的rejectExecution
方法来通知调用者,默认情况下会抛出一个RejectExecutionExeception
异常。
下面是
ThreadPoolExecutor
的任务执行规则:
- 如果线程池的数量还未到达核心线程数量,那么会直接启动一个核心线程来执行任务。
- 如果线程池中的线程数量已经达到或者超出核心线程的数量,那么任务会被插入到任务队列中等待执行。
- 如果在步骤
2
中无法将任务插入到任务队列中,这往往是由于任务队列已满,这个时候如果线程数量未达到线程池规定的最大值,那么会立即启动一个非核心线程来执行任务。 - 如果在步骤
3
中线程数量已经达到线程池规定的最大值,那么就会拒绝执行此任务,ThreadPoolExecutor
会调用RejectExecutionHandler
的rejectExecution
方法来通知调用者。
# 线程池的适用场景
返回类型 | 方法名称 | 适用场景 |
---|---|---|
ExecutorService | newFixedThreadPool(int nThreads) | 适用于为了满足资源管理需求,而需要限制当前线程的数量的应用场景,它适用于负载比较重的服务器。 |
ExecutorService | newCachedThreadPool() | 大小无界的线程池适用于执行很多的短期异步任务的小程序,或负载较轻的服务器。 |
ExecutorService | newSingleThreadExecutor() | 适用于需要保证执行顺序得执行各个任务,并且在任意时间点,不会有多个线程是活动的场景。 |
# 三种常见线程池
三种常见的线程池,它们都直接或间接地通过配置
ThreadPoolExecutor
来实现自己的功能特性,这三种常见线程池分别是FixedThreadPool
、CachedThreadPool
、SingleThreadExecutor
,下面分别来介绍。
# FixedThreadPool
FixedThreadPool
模式会使用一个优先固定数量的线程,来处理若干数量任务,规定数量的线程处理所有任务,一旦有线程处理完了任务就会被用来处理新任务,当然是需要有新任务的前提下。FixedThreadPool
模式下最多的线程数量是一定的,FixedThreadPool
对象创建代码如下:
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5); |
FixedThreadPool
创建方法的源码如下:
package java.util.concurrent; | |
public class Executors { | |
public static ExecutorService newFixedThreadPool(int nThreads) { | |
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); | |
} | |
} |
FixedThreadPool
的corePoolSize
和maximumPoolSize
参数都被设置为nThreads
,当线程池中的线程数量大于corePoolSize
时keepAliveTime
为非核心空闲线程等待新任务的最长时间,超过这个时间后非核心线程将会被终止,keepAliveTime
设置为0L
说明非核心线程会立即被终止,实际了也并没有非核心线程创建,因为核心线程和最大线程数都是一样的。
下图为
FixedThreadPool
的execute()
方法的执行流程。
- 如果当前运行线程数小于
corePoolSize
时,则创建一个新线程来执行任务。 - 如果当前线程池的运行线程数等于
corePoolSize
,那么后面提交的任务将加入到LinkedBlockingQueue
队列中进行等待。 - 线程在执行完图中的
1
后,会在循环中反复从LinkedBlockingQueue
队列中获取任务来执行。- 另外需要知道的一点是
FixedThreadPool
使用的是无界队列LinkedBlockingQueue
作为线程池的工作队列,队列容量为Integer.MAX_VALUE
。
- 另外需要知道的一点是
当然使用该队列作为工作队列会对线程池产生如下影响。
- 当前线程池中的线程数量达到
corePoolSize
后,新的任务将在无界队列中等待。 - 由于使用的是无界队列,所以参数
maximumPoolSize
和keepAliveTime
无效。 - 由于使用无界队列,运行中的
FixedThreadPool
不会拒绝任务,当然此时是未执行shutdown
和shutdownNow
方法,所以不会去调用RejectExecutionHandler
的rejectExecution
方法抛出异常。
下面示例原文出自
《Java编程思想》
。
package top.rem.rain.demo3; | |
import java.util.concurrent.ExecutorService; | |
import java.util.concurrent.Executors; | |
/** | |
* @Author: LightRain | |
* @Description: 使用 FixedThreadPool 设置三个线程来执行五个任务 | |
* @DateTime: 2024-01-11 03:31 | |
* @Version:1.0 | |
**/ | |
public class FixedThreadPoolDemo implements Runnable { | |
protected int countDown = 10; | |
private static int taskCount = 0; | |
private final int id = taskCount++; | |
public FixedThreadPoolDemo() { | |
} | |
public FixedThreadPoolDemo(int countDown) { | |
this.countDown = countDown; | |
} | |
public String status() { | |
return "#" + id + "(" + (countDown > 0 ? countDown : "FixedThreadPoolDemo!") + ") "; | |
} | |
@Override | |
public void run() { | |
while (countDown-- > 0) { | |
System.out.print(status()); | |
Thread.yield(); | |
} | |
} | |
public static void main(String[] args) { | |
// 三个线程来执行五个任务 | |
ExecutorService exec = Executors.newFixedThreadPool(3); | |
for (int i = 0; i < 5; i++) { | |
exec.execute(new FixedThreadPoolDemo()); | |
} | |
exec.shutdown(); | |
} | |
} |
# CachedThreadPool
CachedThreadPool
首先会按照需要创建足够多的线程来执行任务(Task)
, 随着程序执行的过程,有的线程执行完了任务可以被重新循环使用时,才不会再创建先的线程来执行任务,创建方式如下:
ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); |
CachedThreadPool
创建方法的源码如下:
public static ExecutorService newCachedThreadPool() { | |
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>()); | |
} |
从该静态方法中可以看出
CachedThreadPool
的corePoolSize
被设置为0
, 而maximumPoolSize
被设置为Integer.MAX_VALUE
, 即maximumPoolSize
是无界的,而keepAliveTime
被设置为60L
单位为秒,也就是空闲线程的等待时间最长为60
秒,超过该时间将会被终止。
而且在这里
CachedThreadPool
使用的是没有容量的SynchronousQueue
作为线程池的工作队列,但其maximumPoolSize
是无界的,这也就意味着如果主线程提交任务的速度高于maximumPoolSize
中线程处理任务的速度时CachedThreadPool
将会不断的创建新的线程,在极端情况下CachedThreadPool
会因为创建过多线程而耗尽CPU
和内存资源。
下图为
CachedThreadPool
的execute()
方法的执行流程。
- 首先执行
SynchronousQueue.offer(Runnable task)
添加一个任务,如果当前CachedThreadPool
中有空闲线程正在执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)
, 其中NANOSECONDS
是微毫秒(微妙/1000)
,那么主线程执行offer
操作与空闲线程执行poll
操作配对成功后,主线程会把任务交给空闲线程来执行,然此时execute()
方法执行完成,否则将进入第2
步。 - 当
CachedThreadPool
初始线程数为空时或者当前没有空闲线程,将没有线程去执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)
, 这样的情况下步骤1
将会失败,此时CachedThreadPool
会创建一个新的线程来执行任务,此时execute()
方法执行完成。 - 在步骤
2
中创建的新线程将任务执行完成后,会执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)
, 这个poll
操作会让空闲线程最多在SynchronousQueue
中等待60
秒,如果60
秒内主线程提交了一个新任务,那么这个空闲线程将会执行主线程提交的新任务,否则这个空闲线程将被终止,由于60
秒的空闲线程会被终止,因此长时间保持空闲的CachedThreadPoll
是不会使用任何资源的。
SynchronousQueue
是一个没有容量的阻塞队列,因为时间到了之后空闲线程就会被移除,每个插入操作必须等到一个线程与之对应。CachedThreadPool
使用SynchronousQueue
队列把主线程的任务传递给空闲线程执行,执行流程如下。
示例代码如下:
package top.rem.rain.demo3; | |
import java.util.concurrent.ExecutorService; | |
import java.util.concurrent.Executors; | |
/** | |
* @Author: LightRain | |
* @Description: CachedThreadPool 的使用 | |
* @DateTime: 2024-01-11 03:31 | |
* @Version:1.0 | |
**/ | |
public class CachedThreadPoolDemo implements Runnable { | |
protected int countDown = 10; | |
private static int taskCount = 0; | |
private final int id = taskCount++; | |
public CachedThreadPoolDemo() { | |
} | |
public CachedThreadPoolDemo(int countDown) { | |
this.countDown = countDown; | |
} | |
public String status() { | |
return "#" + id + "(" + (countDown > 0 ? countDown : "FixedThreadPoolDemo!") + ") "; | |
} | |
@Override | |
public void run() { | |
while (countDown-- > 0) { | |
System.out.print(status()); | |
Thread.yield(); | |
} | |
} | |
public static void main(String[] args) { | |
// 三个线程来执行五个任务 | |
ExecutorService exec = Executors.newCachedThreadPool(); | |
for (int i = 0; i < 10; i++) { | |
exec.execute(new CachedThreadPoolDemo()); | |
} | |
exec.shutdown(); | |
} | |
} |
# SingleThreadExecutor
SingleThreadExecutor
模式只会创建一个线程,它和FixedThreadPool
比较类似,不过线程数是一个。如果多个任务被提交给SingleThreadExecutor
的话,那么这些任务会被保存在一个队列中,并且会按照任务提交的顺序来一个个的执行。
SingleThreadExecutor
模式可以保证只有一个任务会被执行,这种特性可以被用来处理共享资源的问题从而不需要考虑同步问题,创建方式如下:
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); |
SingleThreadExecutor
创建方法的源码如下:
public static ExecutorService newSingleThreadExecutor() { | |
return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>())); | |
} |
从静态方法中可以看出
SingleThreadExecutor
的corePoolSize
和maximumPoolSize
被设置为1
, 其它参数则与FixedThreadPool
相同。
SingleThreadExecutor
使用的工作队也是LinkedBlockingQueue
无界队,由于SingleThreadExecutor
采用无界队列对线程池的影响与FixedThreadPool
是一样的,执行流程如下:
- 如果当前线程数少于
corePoolSize
即线程池中没有线程运行,则创建一个新的线程来执行任务。 - 如果当前线程数等于
corePoolSize
时,会将任务加入到LinkedBlockingQueue
队列中。 - 线程执行完成步骤
1
中的任务后,会在一个无限循环中反复从LinkedBlockingQueue
队列中获取任务来执行。
示例代码如下:
package top.rem.rain.demo3; | |
import java.util.concurrent.ExecutorService; | |
import java.util.concurrent.Executors; | |
/** | |
* @Author: LightRain | |
* @Description: SingleThreadExecutor 的使用 | |
* @DateTime: 2024-01-11 03:31 | |
* @Version:1.0 | |
**/ | |
public class SingleThreadExecutorDemo implements Runnable { | |
protected int countDown = 10; | |
private static int taskCount = 0; | |
private final int id = taskCount++; | |
public SingleThreadExecutorDemo() { | |
} | |
public SingleThreadExecutorDemo(int countDown) { | |
this.countDown = countDown; | |
} | |
public String status() { | |
return "#" + id + "(" + (countDown > 0 ? countDown : "FixedThreadPoolDemo!") + ") "; | |
} | |
@Override | |
public void run() { | |
while (countDown-- > 0) { | |
System.out.print(status()); | |
Thread.yield(); | |
} | |
} | |
public static void main(String[] args) { | |
// 三个线程来执行五个任务 | |
ExecutorService exec = Executors.newSingleThreadExecutor(); | |
for (int i = 0; i < 2; i++) { | |
exec.execute(new SingleThreadExecutorDemo()); | |
} | |
exec.shutdown(); | |
} | |
} |
# ScheduledThreadPoolExecutor
# 执行机制
ScheduledThreadPoolExecutor
继承自ThreadPoolExecutor
,它主要用来在给定的延迟之后执行任务或定期执行任务。
ScheduledThreadPoolExecutor
的功能与Timer
类似,但它比Timer
更强大更灵活,Timer
对应的是单个后台线程,而ScheduledThreadPoolExecutor
可以在构造函数中指定多个对应的后台线程数。
ScheduledThreadPoolExecutor
执行机制如下:
DelayQueue
是一个无界队列,所以ThreadPoolExecutor
的maximumPoolSize
在ScheduledThreadPoolExecutor
中无效,ScheduledThreadPoolExecutor
的执行主要分为以下两部分:
- 当调用
ScheduledThreadPoolExecutor
的scheduleAtFixedRate()
或scheduleWithFixedDelay()
方法时会向ScheduleThreadPoolExecutor
的DelayQueue
中添加一个实现了RunnableScheduledFuture
接口的ScheduleFutureTask
。 - 线程池中的线程从
DelayQueue
中获取ScheduleFutureTask
然后来执行任务。
# ScheduledThreadPoolExecutor 的创建
ScheduledThreadPoolExecutor
通常使用工厂类Executors
来创建,Executors
可以创建两种类型的ScheduledThreadPoolExecutor
。
ScheduledThreadPoolExecutor
:可以执行并行任务也就是多条线程同时执行。SingleThreadScheduledExecutor
:可以执行单条线程。
ScheduledThreadPoolExecutor
构造方法如下:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) | |
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory) |
SingleThreadScheduledExecutor
构造方法如下:
public static ScheduledExecutorService newSingleThreadScheduledExecutor() | |
public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) |
创建实例对象代码如下:
// 创建 ScheduledThreadPool | |
ScheduledExecutorService scheduledThreadPoolExecutor = Executors.newScheduledThreadPool(5); | |
// 创建 SingleThreadScheduledExecutor | |
ScheduledExecutorService singleThreadScheduledExecutor = Executors.newSingleThreadScheduledExecutor(); |
# 适用场景
ScheduledThreadPoolExecutor
:适用于多个后台线程执行周期性任务,同时为了满足资源管理的需求而需要限制后台线程数量的应用程序。SingleThreadScheduledExecutor
:适用于需要单个后台线程执行周期任务,同时需要保证任务顺序执行的应用场景。
# ScheduledThreadPoolExecutor 的使用
首先创建一个
Runnable
的对象,然后使用ScheduledThreadPoolExecutor
的schedule()
方法来执行延迟任务。
public ScheduledFuture<?> schedule(Runnable command,long delay,TimeUnit unit) |
command
:实现Runnable
接口的类。delay
:延迟多久后执行。unit
:用于指定keepAliveTime
参数的时间单位,这是一个枚举,常用的有TimeUnit.MILLISECONDS(毫秒)
、TimeUnit.SECONDS(秒)
以及TimeUnit.MINUTES(分钟)
等。- 注意:此方法会返回一个
ScheduledFuture
实例,可以用于获取线程状态信息和延迟时间。
package top.rem.rain.demo3; | |
import java.text.SimpleDateFormat; | |
import java.util.Date; | |
import java.util.concurrent.Executors; | |
import java.util.concurrent.ScheduledExecutorService; | |
import java.util.concurrent.TimeUnit; | |
/** | |
* @Author: LightRain | |
* @Description: 延迟执行 | |
* @DateTime: 2024-01-11 19:18 | |
* @Version:1.0 | |
**/ | |
public class ScheduledThreadPoolDemo implements Runnable { | |
@Override | |
public void run() { | |
System.out.println(Thread.currentThread().getName() + " Start. Time = " + getNowDate()); | |
threadSleep(); | |
System.out.println(Thread.currentThread().getName() + " End. Time = " + getNowDate()); | |
} | |
/** | |
* 睡 3 秒 | |
*/ | |
public void threadSleep() { | |
try { | |
Thread.sleep(3000); | |
} catch (InterruptedException e) { | |
e.printStackTrace(); | |
} | |
} | |
/** | |
* 获取现在时间 | |
* | |
* @return 返回时间类型 yyyy-MM-dd HH:mm:ss | |
*/ | |
public static String getNowDate() { | |
Date currentTime = new Date(); | |
SimpleDateFormat formatter; | |
formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); | |
return formatter.format(currentTime); | |
} | |
public static void main(String[] args) { | |
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5); | |
try { | |
// 计划在某个时间之后运行 | |
System.out.println("Current Time = " + getNowDate()); | |
for (int i = 0; i < 3; i++) { | |
Thread.sleep(1000); | |
ScheduledThreadPoolDemo worker = new ScheduledThreadPoolDemo(); | |
// 延迟 10 秒后执行 | |
scheduledThreadPool.schedule(worker, 10, TimeUnit.SECONDS); | |
} | |
Thread.sleep(3000); | |
} catch (InterruptedException e) { | |
e.printStackTrace(); | |
} | |
scheduledThreadPool.shutdown(); | |
while (!scheduledThreadPool.isTerminated()) { | |
// 等待所有任务完成 | |
} | |
System.out.println("已完成所有线程"); | |
} | |
} |
当前时间 = 2024-01-11 19:27:10 | |
pool-1-thread-1 Start. Time = 2024-01-11 19:27:21 | |
pool-1-thread-3 Start. Time = 2024-01-11 19:27:22 | |
pool-1-thread-2 Start. Time = 2024-01-11 19:27:23 | |
pool-1-thread-1 End. Time = 2024-01-11 19:27:24 | |
pool-1-thread-3 End. Time = 2024-01-11 19:27:25 | |
pool-1-thread-2 End. Time = 2024-01-11 19:27:26 | |
已完成所有线程 |
从执行结果可以看出,线程任务确实在
10
秒延迟后才开始执行,这就是schedule()
方法的使用,下面是两个可用于周期性执行任务的方法。
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit); |
scheduleAtFixedRate()
方法的作用是预定在初始的延迟结束后,周期性得执行给定的任务,周期长度为period
其中initialDelay
为初始延迟,将按照固定的时间来执行,即到点执行。
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit); |
scheduleWithFixedDelay
方法的作用是预定在初始的延迟结束后周期性得执行给定任务,在因此调用完成和下一次调用开始之间有长度为delay
的延迟,其中initialDelay
为初始延迟,可以简单的理解为等上一个任务结束后,在等固定的时间然后执行。
package top.rem.rain.demo3; | |
import java.util.Date; | |
import java.util.concurrent.ScheduledThreadPoolExecutor; | |
import java.util.concurrent.TimeUnit; | |
/** | |
* @Author: LightRain | |
* @Description: 周期方法测试类 | |
* @DateTime: 2024-01-11 19:51 | |
* @Version:1.0 | |
**/ | |
public class ScheduledTask { | |
public ScheduledThreadPoolExecutor se = new ScheduledThreadPoolExecutor(5); | |
public void fixedPeriodSchedule() { | |
// 设定可以循环执行的 runnable, 初始延迟为 0,这里设置的任务的间隔为 3 秒 | |
for (int i = 0; i < 5; i++) { | |
se.scheduleAtFixedRate(new FixedSchedule(), 0, 3, TimeUnit.SECONDS); | |
} | |
} | |
public ScheduledTask() { | |
fixedPeriodSchedule(); | |
} | |
static class FixedSchedule implements Runnable { | |
public void run() { | |
System.out.println("当前线程:" + Thread.currentThread().getName() + " 当前时间:" + new Date(System.currentTimeMillis())); | |
} | |
} | |
public static void main(String[] args) { | |
new ScheduledTask(); | |
} | |
} |
当前线程:pool-1-thread-4 当前时间:Thu Jan 11 19:52:52 CST 2024 | |
当前线程:pool-1-thread-3 当前时间:Thu Jan 11 19:52:52 CST 2024 | |
当前线程:pool-1-thread-5 当前时间:Thu Jan 11 19:52:52 CST 2024 | |
当前线程:pool-1-thread-2 当前时间:Thu Jan 11 19:52:52 CST 2024 | |
当前线程:pool-1-thread-1 当前时间:Thu Jan 11 19:52:52 CST 2024 | |
当前线程:pool-1-thread-5 当前时间:Thu Jan 11 19:52:55 CST 2024 | |
当前线程:pool-1-thread-3 当前时间:Thu Jan 11 19:52:55 CST 2024 | |
当前线程:pool-1-thread-1 当前时间:Thu Jan 11 19:52:55 CST 2024 | |
当前线程:pool-1-thread-4 当前时间:Thu Jan 11 19:52:55 CST 2024 | |
当前线程:pool-1-thread-2 当前时间:Thu Jan 11 19:52:55 CST 2024 | |
当前线程:pool-1-thread-1 当前时间:Thu Jan 11 19:52:58 CST 2024 | |
当前线程:pool-1-thread-2 当前时间:Thu Jan 11 19:52:58 CST 2024 | |
当前线程:pool-1-thread-5 当前时间:Thu Jan 11 19:52:58 CST 2024 | |
当前线程:pool-1-thread-1 当前时间:Thu Jan 11 19:52:58 CST 2024 | |
当前线程:pool-1-thread-4 当前时间:Thu Jan 11 19:52:58 CST 2024 |
而
SingleThreadScheduledExecutor
的使用的方法基本是类似的,只不过是单线程罢了。