不管是在 Java 中还是在 Android 中使用到的线程池都是一样的,那就是 Executor 线程池框架。

# Executor 框架是什么?

Executor 框架是实现线程池的功能,我们知道线程池就是线程的集合,在线程池中来管理线程,以实现线程的重用性,降低资源消耗,提高响应速度,线程用于执行异步任务,单个线程既是工作单元也是执行机制,从 JDK 1.5 开始为了把工作单元与执行机制分离开,然后就诞生了 Executor 框架,它是一个用于统一创建与运行的接口。

Executor 框架是 Java 并发编程中的一个重要组成部分,它提供了一种标准的方式来执行任务,它包含 ExecutorExecutorServiceCallableFuture 等接口和类,可以有效地管理线程的生命周期、执行任务以及获取任务的执行结果。

我们还需要明白另一个问题,为什么需要线程池?在 Java 中使用线程来执行异步任务时,线程的创建和销毁需要一定的开销,如果我们为每一个任务创建一个新线程来执行的话,那么这些线程的创建与销毁将消耗大量的计算资源,同时为每一个任务创建一个新线程来执行,这样的方式可能会使处于高负荷状态的应用产生崩溃。

此时线程池的出现将为解决这个问题带来了曙光,我们可以在线程池中创建若干条线程,当有任务需要执行时就从该线程池中获取一条线程来执行任务,如果一时间的任务过多并超出了线程池中的数量,那么后面的线程任务将进入一个等待队列进行等待,直到线程池中有线程处于空闲时才从等待队列获取要执行的任务进行处理,以此循环,这样就大大减少了线程创建与销毁的开销,当然也会缓解应用处于超负荷时的情况。

# Executor 两级调度模型

Java 线程启动时会创建一个本地操作系统线程,当该 Java 线程终止时,这个操作系统线程也将会被回收,而每一个 Java 线程都会被一对一映射为本地操作系统的线程,操作系统会调度所有的线程并将它们分给可用的 CPU

所谓的映射方式的实现,在上层 Java 多线程程序通过把应用分为若干个任务,然后使用用户级的调度器 (Executor框架) 将这些任务映射为固定数量的线程,在底层操作系统内核将这些线程映射到硬件处理器上,两级调度模型示意图如下:

pFpTG1s.png

从上图中可以看出应用程序通过 Executor 框架控制上层的调度,而下层的调度由操作系统内核来控制,下层的调度不受应用程序的控制。

# Executor 框架结构

  • Executor 框架结构主要包括三个部分:
    1. 任务:包括被执行任务需要实现的接口 RunnableCallable 接口。
    2. 任务的执行:包括任务执行机制的核心接口 Executor 以及继承自 ExecutorExecutorService 接口, Executor 有两个关键类实现了 ExecutorService 接口,它们分别是 ThreadPoolExecutorScheduledThreadPoolExecutor 类。
    3. 异步计算的结果:包括接口 Future 和实现 Future 接口的 FutureTask 类。
  • 下面是通过 UML 类图展示这些类之间的关系,关系图如下:

pFpT0NF.png

  • Extecutor 是一个接口,它是 Executor 框架的基础,它将任务的提交与任务的执行分离开来。
  • ThreadPoolExecutor 是线程池的核心实现类,它用来执行被提交的任务。
  • ScheduledThreadPoolExecutor 是一个实现类,它可以在给定的延迟后运行命令或定期执行命令,它比 Timer 更灵活,功能更强大。
  • Future 接口和实现 Future 接口的 FutureTask 类代表异步计算的结果。
  • Runnable 接口和 Callable 接口的实现类都可以被 ThreadPoolExecutorScheduleThreadPoolExecutor 类执行,区别就是 Runnable 无法返回执行结果,而 Callable 可以返回执行结果。
  • 执行关系图如下:

pFpTgnx.png

在上图中主线程首先创建实现 RunnableCallable 接口的任务对象,工具类 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 :线程池的核心线程数,在默认情况下,核心线程数会一直在线程池中存活,即使它们处于闲置状态。
    • 如果将 ThreadPoolExecutorallowCoreThreadTimeOut 属性设置为 true ,那么闲置的核心线程在等待新任务到来时会执行超时策略,这个时间间隔由 keepAliveTime 所指定,当等待时间超过 keepAliveTime 所指定的时长后,核心线程将被终止。
  • maximumPoolSize :线程池所能容纳的最大线程数量,当活动线程数到达这个数值后,后续的新任务将会被阻塞。
  • keepAliveTime :非核心线程闲置时的超时时长,超过这个时长,非核心线程就会被回收。
    • ThreadPoolExecutorallowCoreThreadTimeOut 属性设置为 true 时, keepAliveTime 同样会作用于核心线程。
  • unit :用于指定 keepAliveTime 参数的时间单位,它是一个枚举类型,常用的有 TimeUnit.MILLISECONDS(毫秒)TimeUnit.SECONDS(秒) 以及 TimeUnit.MINUTES(分钟) 等。
  • workQueue :线程池中的任务队列,通过线程池的 execute 方法提交 Runnable 对象会存储在这个队列中。
  • threadFactory :线程工厂,为线程池提供创建新线程的功能。
    • ThreadFactory 是一个接口,它只有一个 newThread(Runnable r) 方法。
    • 还有一个不常用的参数那就是 RejectExecutionHandler ,它表示当 ThreadPoolExecutor 已经关闭或 ThreadPoolExecutor 已经达到了最大线程池大小而且工作队列已满, execute 方法将会调用 HandlerrejectExecution 方法来通知调用者,默认情况下会抛出一个 RejectExecutionExeception 异常。

下面是 ThreadPoolExecutor 的任务执行规则:

  1. 如果线程池的数量还未到达核心线程数量,那么会直接启动一个核心线程来执行任务。
  2. 如果线程池中的线程数量已经达到或者超出核心线程的数量,那么任务会被插入到任务队列中等待执行。
  3. 如果在步骤 2 中无法将任务插入到任务队列中,这往往是由于任务队列已满,这个时候如果线程数量未达到线程池规定的最大值,那么会立即启动一个非核心线程来执行任务。
  4. 如果在步骤 3 中线程数量已经达到线程池规定的最大值,那么就会拒绝执行此任务, ThreadPoolExecutor 会调用 RejectExecutionHandlerrejectExecution 方法来通知调用者。

# 线程池的适用场景

返回类型方法名称适用场景
ExecutorServicenewFixedThreadPool(int nThreads)适用于为了满足资源管理需求,而需要限制当前线程的数量的应用场景,它适用于负载比较重的服务器。
ExecutorServicenewCachedThreadPool()大小无界的线程池适用于执行很多的短期异步任务的小程序,或负载较轻的服务器。
ExecutorServicenewSingleThreadExecutor()适用于需要保证执行顺序得执行各个任务,并且在任意时间点,不会有多个线程是活动的场景。

# 三种常见线程池

三种常见的线程池,它们都直接或间接地通过配置 ThreadPoolExecutor 来实现自己的功能特性,这三种常见线程池分别是 FixedThreadPoolCachedThreadPoolSingleThreadExecutor ,下面分别来介绍。

# FixedThreadPool

FixedThreadPool 模式会使用一个优先固定数量的线程,来处理若干数量任务,规定数量的线程处理所有任务,一旦有线程处理完了任务就会被用来处理新任务,当然是需要有新任务的前提下。 FixedThreadPool 模式下最多的线程数量是一定的, FixedThreadPool 对象创建代码如下:

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);

FixedThreadPool 创建方法的源码如下:

Executors.java
package java.util.concurrent;
public class Executors {
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
    }
}

FixedThreadPoolcorePoolSizemaximumPoolSize 参数都被设置为 nThreads ,当线程池中的线程数量大于 corePoolSizekeepAliveTime 为非核心空闲线程等待新任务的最长时间,超过这个时间后非核心线程将会被终止, keepAliveTime 设置为 0L 说明非核心线程会立即被终止,实际了也并没有非核心线程创建,因为核心线程和最大线程数都是一样的。

下图为 FixedThreadPoolexecute() 方法的执行流程。

pF9BQZq.png

  1. 如果当前运行线程数小于 corePoolSize 时,则创建一个新线程来执行任务。
  2. 如果当前线程池的运行线程数等于 corePoolSize ,那么后面提交的任务将加入到 LinkedBlockingQueue 队列中进行等待。
  3. 线程在执行完图中的 1 后,会在循环中反复从 LinkedBlockingQueue 队列中获取任务来执行。
    • 另外需要知道的一点是 FixedThreadPool 使用的是无界队列 LinkedBlockingQueue 作为线程池的工作队列,队列容量为 Integer.MAX_VALUE

当然使用该队列作为工作队列会对线程池产生如下影响。

  1. 当前线程池中的线程数量达到 corePoolSize 后,新的任务将在无界队列中等待。
  2. 由于使用的是无界队列,所以参数 maximumPoolSizekeepAliveTime 无效。
  3. 由于使用无界队列,运行中的 FixedThreadPool 不会拒绝任务,当然此时是未执行 shutdownshutdownNow 方法,所以不会去调用 RejectExecutionHandlerrejectExecution 方法抛出异常。

下面示例原文出自 《Java编程思想》

FixedThreadPoolDemo.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>());
}

从该静态方法中可以看出 CachedThreadPoolcorePoolSize 被设置为 0 , 而 maximumPoolSize 被设置为 Integer.MAX_VALUE , 即 maximumPoolSize 是无界的,而 keepAliveTime 被设置为 60L 单位为秒,也就是空闲线程的等待时间最长为 60 秒,超过该时间将会被终止。

而且在这里 CachedThreadPool 使用的是没有容量的 SynchronousQueue 作为线程池的工作队列,但其 maximumPoolSize 是无界的,这也就意味着如果主线程提交任务的速度高于 maximumPoolSize 中线程处理任务的速度时 CachedThreadPool 将会不断的创建新的线程,在极端情况下 CachedThreadPool 会因为创建过多线程而耗尽 CPU 和内存资源。

下图为 CachedThreadPoolexecute() 方法的执行流程。

pF9BGJU.png

  1. 首先执行 SynchronousQueue.offer(Runnable task) 添加一个任务,如果当前 CachedThreadPool 中有空闲线程正在执行 SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS) , 其中 NANOSECONDS 是微毫秒 (微妙/1000) ,那么主线程执行 offer 操作与空闲线程执行 poll 操作配对成功后,主线程会把任务交给空闲线程来执行,然此时 execute() 方法执行完成,否则将进入第 2 步。
  2. CachedThreadPool 初始线程数为空时或者当前没有空闲线程,将没有线程去执行 SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS) , 这样的情况下步骤 1 将会失败,此时 CachedThreadPool 会创建一个新的线程来执行任务,此时 execute() 方法执行完成。
  3. 在步骤 2 中创建的新线程将任务执行完成后,会执行 SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS) , 这个 poll 操作会让空闲线程最多在 SynchronousQueue 中等待 60 秒,如果 60 秒内主线程提交了一个新任务,那么这个空闲线程将会执行主线程提交的新任务,否则这个空闲线程将被终止,由于 60 秒的空闲线程会被终止,因此长时间保持空闲的 CachedThreadPoll 是不会使用任何资源的。

SynchronousQueue 是一个没有容量的阻塞队列,因为时间到了之后空闲线程就会被移除,每个插入操作必须等到一个线程与之对应。 CachedThreadPool 使用 SynchronousQueue 队列把主线程的任务传递给空闲线程执行,执行流程如下。

pF9BoY8.png

示例代码如下:

CachedThreadPoolDemo.java
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>()));
}

从静态方法中可以看出 SingleThreadExecutorcorePoolSizemaximumPoolSize 被设置为 1 , 其它参数则与 FixedThreadPool 相同。

SingleThreadExecutor 使用的工作队也是 LinkedBlockingQueue 无界队,由于 SingleThreadExecutor 采用无界队列对线程池的影响与 FixedThreadPool 是一样的,执行流程如下:

pF9zAKS.png

  1. 如果当前线程数少于 corePoolSize 即线程池中没有线程运行,则创建一个新的线程来执行任务。
  2. 如果当前线程数等于 corePoolSize 时,会将任务加入到 LinkedBlockingQueue 队列中。
  3. 线程执行完成步骤 1 中的任务后,会在一个无限循环中反复从 LinkedBlockingQueue 队列中获取任务来执行。

示例代码如下:

SingleThreadExecutorDemo.java
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 执行机制如下:

pFC9zsf.png

DelayQueue 是一个无界队列,所以 ThreadPoolExecutormaximumPoolSizeScheduledThreadPoolExecutor 中无效, ScheduledThreadPoolExecutor 的执行主要分为以下两部分:

  1. 当调用 ScheduledThreadPoolExecutorscheduleAtFixedRate()scheduleWithFixedDelay() 方法时会向 ScheduleThreadPoolExecutorDelayQueue 中添加一个实现了 RunnableScheduledFuture 接口的 ScheduleFutureTask
  2. 线程池中的线程从 DelayQueue 中获取 ScheduleFutureTask 然后来执行任务。

# ScheduledThreadPoolExecutor 的创建

ScheduledThreadPoolExecutor 通常使用工厂类 Executors 来创建, Executors 可以创建两种类型的 ScheduledThreadPoolExecutor

  1. ScheduledThreadPoolExecutor :可以执行并行任务也就是多条线程同时执行。
  2. 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 的对象,然后使用 ScheduledThreadPoolExecutorschedule() 方法来执行延迟任务。

public ScheduledFuture<?> schedule(Runnable command,long delay,TimeUnit unit)
  • command :实现 Runnable 接口的类。
  • delay :延迟多久后执行。
  • unit :用于指定 keepAliveTime 参数的时间单位,这是一个枚举,常用的有 TimeUnit.MILLISECONDS(毫秒)TimeUnit.SECONDS(秒) 以及 TimeUnit.MINUTES(分钟) 等。
  • 注意:此方法会返回一个 ScheduledFuture 实例,可以用于获取线程状态信息和延迟时间。
ScheduledThreadPoolDemo.java
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 的使用的方法基本是类似的,只不过是单线程罢了。