调度线程池ScheduledThreadPoolExecutor的正确使用姿势

简介: 调度线程池ScheduledThreadPoolExecutor的正确使用姿势

前言


项目中经常会遇到一些非分布式的调度任务,需要在未来的某个时刻周期性执行。实现这样的功能,我们有多种方式可以选择:

  1. Timer类, jdk1.3引入,不推荐

它所有任务都是串行执行的,同一时间只能有一个任务在执行,而且前一个任务的延迟或异常都将会影响到之后的任务。

  1. Spring的@Scheduled注解,不是很推荐

这种方式底层虽然是用线程池实现,但是有个最大的问题,所有的任务都使用的同一个线程池,可能会导致长周期的任务运行影响短周期任务运行,造成线程池"饥饿",更加推荐的做法是同种类型的任务使用同一个线程池。

  1. 自定义ScheduledThreadPoolExecutor实现调度任务

这也是本文重点讲解的方式,通过自定义ScheduledThreadPoolExecutor调度线程池,提交调度任务才是最优解。


创建方式


创建ScheduledThreadPoolExecutor方式一共有两种,第一种是通过自定义参数,第二种通过Executors工厂方法创建。 根据阿里巴巴代码规范中的建议,更加推荐我们使用第一种方式创建。

  1. 自定义参数创建
ScheduledThreadPoolExecutor(int corePoolSize,
                                       ThreadFactory threadFactory,
                                       RejectedExecutionHandler handler)
复制代码
  • corePoolSize:核心工作的线程数量
  • threadFactory:线程工厂,用来创建线程
  • handler: 拒绝策略,饱和策略
  1. Executors工厂方法创建
  • static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

根据核心线程数创建调度线程池。

  • static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory)

根据核心线程数和线程工厂创建调度线程池。


核心API


  1. schedule(Runnable command, long delay, TimeUnit unit)

创建并执行在给定延迟后启用的一次性操作

  • command: 执行的任务
  • delay:延迟的时间
  • unit: 单位
  1. scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)

定时执行周期任务,任务执行完成后,延迟delay时间执行

  • command: 执行的任务
  • initialDelay: 初始延迟的时间
  • delay: 上次执行结束,延迟多久执行
  • unit:单位
@Test
public void testScheduleWithFixedDelay() throws InterruptedException {
    // 创建调度任务线程池
    ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(1);
    // 按照上次执行完成后固定延迟时间调度
    scheduledExecutorService.scheduleWithFixedDelay(() -> {
        try {
            log.info("scheduleWithFixedDelay ...");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }, 1, 2, TimeUnit.SECONDS);
    Thread.sleep(10000);
}

1671196261388.jpg

  1. scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)

按照固定的评率定时执行周期任务,不受任务运行时间影响。

  • command: 执行的任务
  • initialDelay: 初始延迟的时间
  • period: 周期
  • unit:单位
@Test
public void testScheduleAtFixedRate() throws InterruptedException {
    // 创建调度任务线程池
    ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(1);
    // 按照固定2秒时间执行
    scheduledExecutorService.scheduleAtFixedRate(() -> {
        try {
            log.info("scheduleWithFixedDelay ...");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }, 1, 2, TimeUnit.SECONDS);
    Thread.sleep(10000);
}

1671196272171.jpg

tips: 以上API全部返回ScheduledExecutorService类,调用调用getDelay()可以获取任务下次的执行时间点,非常好用的。

@Test
public void testResp() throws InterruptedException {
    // 创建调度任务线程池
    ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(1);
    // 按照上次执行完成后固定延迟5秒时间调度
    ScheduledFuture<?> scheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> {
        log.info("exec schedule ...");
    }, 1, 5, TimeUnit.SECONDS);
    while (true) {
        // 获取剩余时间
        long delay = scheduledFuture.getDelay(TimeUnit.SECONDS);
        log.info("下次执行剩余时间{}秒", delay);
       Thread.sleep(1000);
    }
}

1671196287091.jpg


综合例子


下面我们演示个例子,通过ScheduledThreadPoolExecutor实现每周四 18:00:00 定时执行任务。

// 通过ScheduledThreadPoolExecutor实现每周四 18:00:00 定时执行任务
@Test
public void test() {
    //  获取当前时间
    LocalDateTime now = LocalDateTime.now();
    System.out.println(now);
    // 获取周四时间
    LocalDateTime time = now.withHour(18).withMinute(0).withSecond(0).withNano(0).with(DayOfWeek.THURSDAY);
    // 如果 当前时间 > 本周周四,必须找到下周周四
    if(now.compareTo(time) > 0) {
        time = time.plusWeeks(1);
    }
    System.out.println(time);
    // initailDelay 代表当前时间和周四的时间差
    // period 一周的间隔时间
    long initailDelay = Duration.between(now, time).toMillis();
    long period = 1000 * 60 * 60 * 24 * 7;
    ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
    pool.scheduleAtFixedRate(() -> {
        System.out.println("running...");
    }, initailDelay, period, TimeUnit.MILLISECONDS);
}


使用注意事项


使用ScheduledThreadPoolExecutor时一定要注意异常处理, 如果使用不当,会导致定时任务不再执行,记住要try catch捕获异常,具体参考这篇文章:ScheduledThreadPoolExecutor踩过最痛的坑

目录
打赏
0
1
1
0
15
分享
相关文章
【JavaSE专栏84】线程让步,一种线程调度的机制
【JavaSE专栏84】线程让步,一种线程调度的机制
132 0
Java线程调度揭秘:从算法到策略,让你面试稳赢!
在社招面试中,关于线程调度和同步的相关问题常常让人感到棘手。今天,我们将深入解析Java中的线程调度算法、调度策略,探讨线程调度器、时间分片的工作原理,并带你了解常见的线程同步方法。让我们一起破解这些面试难题,提升你的Java并发编程技能!
75 16
linux线程调度策略
linux线程调度策略
121 0
.net core 非阻塞的异步编程 及 线程调度过程
【11月更文挑战第12天】本文介绍了.NET Core中的非阻塞异步编程,包括其基本概念、实现方式及应用示例。通过`async`和`await`关键字,程序可在等待I/O操作时保持线程不被阻塞,提高性能。文章还详细说明了异步方法的基础示例、线程调度过程、延续任务机制、同步上下文的作用以及如何使用`Task.WhenAll`和`Task.WhenAny`处理多个异步任务的并发执行。
STA模型、同步上下文和多线程、异步调度
【10月更文挑战第19天】本文介绍了 STA 模型、同步上下文和多线程、异步调度的概念及其优缺点。STA 模型适用于单线程环境,确保资源访问的顺序性;同步上下文和多线程提高了程序的并发性和响应性,但增加了复杂性;异步调度提升了程序的响应性和资源利用率,但也带来了编程复杂性和错误处理的挑战。选择合适的模型需根据具体应用场景和需求进行权衡。
进程中的线程调度
进程是应用程序运行的基本单位,包括主线程、用户线程和守护线程。计算机由存储器和处理器协同操作,操作系统设计为分时和分任务模式。在个人PC普及后,基于用户的时间片异步任务操作系统确保了更好的体验和性能。线程作为进程的调度单元,通过覆写`Thread`类的`run`方法来处理任务数据,并由系统调度框架统一管理。微服务架构进一步将应用分解为多个子服务,在不同节点上执行,提高数据处理效率与容错性,特别是在大规模数据存储和处理中表现显著。例如,利用微服务框架可以优化算法,加速业务逻辑处理,并在不同区块间分配海量数据存储任务。
Linux进程/线程的调度机制介绍:详细解析Linux系统中进程/线程的调度优先级规则
Linux进程/线程的调度机制介绍:详细解析Linux系统中进程/线程的调度优先级规则
1956 0
Java中的线程调度与性能优化技巧
Java中的线程调度与性能优化技巧
深入理解Java中的多线程调度策略
深入理解Java中的多线程调度策略
|
9月前
|
【操作系统】处理机调度的基本概念和三个层次、进程调度的时机和方式、调度器、闲逛线程
【操作系统】处理机调度的基本概念和三个层次、进程调度的时机和方式、调度器、闲逛线程
749 3
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等