常用定时任务介绍

1.Timer 类

介绍

  • 定时任务由 TimerTimerTask 两个类配合完成。
  • 创建一个 Timer 就是启动一个新线程,其中创建Timer时传true,可以设置此线程为守护线程;TimerTask类为抽象类,实现了Runnable接口,主要用来创建一个新的线程执行任务。
  • Date time 为执行任务的日期,如果该日期早于当前时间,那么程序启动后,任务会立即执行
  • 一个Timer可以执行多个TimerTask任务,TimerTask任务以队列的方式一个个被顺序执行,当前面任务执行时间较长时,会使得后边任务的运行时间也会被延迟,从而出现执行时间于预期时间不一致的情况

schedule方法:
(1)schedule(TimerTask task, Date time):安排在指定的时间执行指定的任务
(2)schedule(TimerTask task, Date firstTime, long period):安排指定的任务在指定的时间开始进行重复的固定延迟执行
(3)schedule(TimerTask task, long delay):安排在指定延迟后执行指定的任务
(4)schedule(TimerTask task, long delay, long period):安排指定的任务在指定的延迟后开始进行重复的固定速率执行

实例

@Component
public class TimerTest {
    private Timer timer1 = new Timer();
    private Timer timer2 = new Timer();

    public void timerTest1() {
        LocalDateTime localDateTime = LocalDateTime.of(2023,1,12,15,16);
        Instant instant = localDateTime.atZone(ZoneId.systemDefault()).toInstant();
        Date startTime = Date.from(instant);
        System.out.println(startTime);

        timer1.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("==================Timer定时任务1执行:" + LocalDateTime.now());
            }
        }, 10000, 20000);
    }

    public void timerTest2() {
        LocalDateTime localDateTime = LocalDateTime.of(2023,1,12,15,17);
        Instant instant = localDateTime.atZone(ZoneId.systemDefault()).toInstant();
        Date startTime = Date.from(instant);
        System.out.println(startTime);

        timer1.schedule(new TimerTask() {
            @SneakyThrows
            @Override
            public void run() {
                System.out.println("==================Timer定时任务2执行:" + LocalDateTime.now());
            }
        }, 5000, 10000);
    }
    
	public void timerTest3() {
        LocalDateTime localDateTime = LocalDateTime.of(2023,1,12,15,17);
        Instant instant = localDateTime.atZone(ZoneId.systemDefault()).toInstant();
        Date startTime = Date.from(instant);
        System.out.println(startTime);

        timer2.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("==================Timer定时任务3执行:" + LocalDateTime.now());
            }
        }, 2000, 5000);
    }

    public void timerTest3() {
        LocalDateTime localDateTime = LocalDateTime.of(2023,1,12,15,17);
        Instant instant = localDateTime.atZone(ZoneId.systemDefault()).toInstant();
        Date startTime = Date.from(instant);
        System.out.println(startTime);

        timer2.schedule(new TimerTask() {
            @SneakyThrows
            @Override
            public void run() {
                System.out.println("==================Timer定时任务4执行:" + LocalDateTime.now());
                throw new Exception();
            }
        }, 1000, 5000);
    }
}

缺点

  • Timer中的一个TimerTask抛出异常,则该Timer不会再运行
  • 单点部署

2.ScheduledExecutorService

介绍

ScheduledExecutor的设计思想是每一个被调度的任务都会由线程池中一个线程去执行,因此任务是并发的,相互之间不会受到干扰;只有当任务的时间到来时,ScheduledExecutor才会真正启动一个线程。

实例

@Component
public class ScheduledExecutorServiceTest {
    private ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);

	// 一次性任务,它会在指定延迟后只执行一次
    public void scheduledTest1() {
        scheduledExecutorService.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("==================scheduled定时任务1执行:" + LocalDateTime.now());
            }
        }, 5, TimeUnit.SECONDS);
    }

	// 2秒后开始执行定时任务,每3秒执行,任务总是以固定时间间隔触发,不管任务执行多长时间
    public void scheduledTest2() {
        scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("==================scheduled定时任务2执行:" + LocalDateTime.now());
            }
        }, 5,5, TimeUnit.SECONDS);
    }

	// 2秒后开始执行定时任务,以3秒为间隔执行,上一次任务执行完毕后,等待固定的时间间隔,再执行下一次任务
    public void scheduledTest3() {
        scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                System.out.println("==================scheduled定时任务3执行:" + LocalDateTime.now());
            }
        }, 5,5, TimeUnit.SECONDS);
    }
    
    // 关闭线程池
	public void shutdown() {
        scheduledExecutorService.shutdown();
    }
}

缺点

  • 单点部署

3.Spring Task

开启定时任务

Spring Boot 默认在无任何第三方依赖的情况下使用 spring-context 模块下提供的定时任务工具 Spring Task。启动类加上 @EnableScheduling 注解。

自定义调度器线程池

@Scheduled 任务调度注解,主要用于配置定时任务;springboot默认的调度器线程池大小为 1。(也就是说在多个方法上加上@schedule的,多个定时任务默认是加入延时队列依次同步执行的)。所以,如果在使用springboot定时器时,有多个定时任务,在使用默认的调度器配置,就会出现排队现象,因为同时只能有一个任务在执行,这个时候当一个任务挂死,那后面的定时任务就不能有效执行了。解决办法就是自定义调度器线程池。

源码分析

// 默认使用的调度器
if(this.taskScheduler == null) {  
    this.localExecutor = Executors.newSingleThreadScheduledExecutor();
    this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
}
// 可以看到SingleThreadScheduledExecutor指定的核心线程为1,说白了就是单线程执行
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
    return new DelegatedScheduledExecutorService
        (new ScheduledThreadPoolExecutor(1));
}
// 利用了DelayedWorkQueue延时队列作为任务的存放队列,这样便可以实现任务延迟执行或者定时执行
public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}

(1)增加配置类,注入TaskScheduler Bean

@Configuration
public class ScheduleConfig {
   
    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        //核心线程池数量,方法: 返回可用处理器的Java虚拟机的数量。
        taskScheduler.setPoolSize(Runtime.getRuntime().availableProcessors() * 2);
        taskScheduler.setThreadNamePrefix("scheduled-thread-");
	    //设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean
	    taskScheduler.setWaitForTasksToCompleteOnShutdown(true);
	    //设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住
	    taskScheduler.setAwaitTerminationSeconds(60);
        return taskScheduler;
    }
}

(2)注入SchedulingConfigurer Bean

@Configuration
public class ScheduleConfig1 implements SchedulingConfigurer {
    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
    	// 直接指定
        scheduledTaskRegistrar.setScheduler(
                new ScheduledThreadPoolExecutor(Runtime.getRuntime().availableProcessors() * 2)
        );

		//也可以自定义的线程池,方便线程的使用与维护,这里不多说了
        scheduledTaskRegistrar.setTaskScheduler(myThreadPoolTaskScheduler);
    }

	@Bean(name = "myThreadPoolTaskScheduler")
    public TaskScheduler getMyThreadPoolTaskScheduler() {
		ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        taskScheduler.setPoolSize(10);
        taskScheduler.setThreadNamePrefix("Haina-Scheduled-");
        taskScheduler.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //调度器shutdown被调用时等待当前被调度的任务完成
        taskScheduler.setWaitForTasksToCompleteOnShutdown(true);
        //等待时长
        taskScheduler.setAwaitTerminationSeconds(60);
        return taskScheduler;
  }       
}

(3)配置文件添加task配置

# 任务调度线程池
# 任务调度线程池大小 默认 1 建议根据任务加大
spring.task.scheduling.pool.size=1
# 调度线程名称前缀 默认 scheduling-
spring.task.scheduling.thread-name-prefix=scheduling-
# 线程池关闭时等待所有任务完成
spring.task.scheduling.shutdown.await-termination=true
# 调度线程关闭前最大等待时间,确保最后一定关闭
spring.task.scheduling.shutdown.await-termination-period=60

# 任务执行线程池配置
# 是否允许核心线程超时。这样可以动态增加和缩小线程池
spring.task.execution.pool.allow-core-thread-timeout=true
#  核心线程池大小 默认 8
spring.task.execution.pool.core-size=8
# 线程空闲等待时间 默认 60s
spring.task.execution.pool.keep-alive=60s
# 线程池最大数  根据任务定制
# spring.task.execution.pool.max-size=
#  线程池 队列容量大小
# spring.task.execution.pool.queue-capacity=
# 线程池关闭时等待所有任务完成
spring.task.execution.shutdown.await-termination=true
# 执行线程关闭前最大等待时间,确保最后一定关闭
# spring.task.execution.shutdown.await-termination-period=
# 线程名称前缀
spring.task.execution.thread-name-prefix=task-

自定义调度器线程池的问题

自定义调度器的方式,会有一个问题:当有足够的空余线程时,多任务时并行执行,但是同一定时任务仍会同步执行(当定时任务的执行时间大于每次执行的时间间隔时即可发现);

配合@Async 注解使用,这样在每次执行定时任务时就新开一个线程,异步非阻塞运行;同时使用这两个注解的效果,相当于@Scheduled仅仅负责调度,而@Async指定的executor负责任务执行,不再使用调度器中的执行器来执行任务。

自定义执行器线程池

  • 在启动类加上 @EnableAsync 注解
  • 在需要异步执行的方法上加 @Async 注解
@Bean("taskExecutor")
public TaskExecutor taskExecutor(){
	ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(corePoolSize);
    executor.setMaxPoolSize(maxPoolSize);
    executor.setQueueCapacity(queueCapacity);
    executor.setKeepAliveSeconds(keepAliveTime);
    executor.setThreadNamePrefix(threadNamePrefix);
    // 线程池对拒绝任务的处理策略
    executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
    // 初始化
    executor.initialize();
    return executor;
}

实例

@Component
public class SpringTaskTest {

    @Scheduled(cron = "0 37 10 * * ?")
    @Async("taskExecutor")  // 指定执行器线程池
    public void SpringTaskTest1() {
        System.out.println("==================Timer定时任务1执行:" + LocalDateTime.now());
        try {
            Thread.sleep(10000);
        } catch (Exception e) {
        }
    }

    @Scheduled(fixedDelay = 5000, initialDelay = 1000)
    @Async("taskExecutor")
    public void SpringTaskTest2() {
        System.out.println("==================Timer定时任务2执行:" + LocalDateTime.now());
        try {
            Thread.sleep(10000);
        } catch (Exception e) {
        }
    }

    @Scheduled(fixedRate = 5000, initialDelay = 1000)
    @Async("taskExecutor")
    public void SpringTaskTest3() {
        System.out.println("==================Timer定时任务3执行:" + LocalDateTime.now());
        try {
            Thread.sleep(10000);
        } catch (Exception e) {
        }
    }
}

Spring Task缺点

  • 默认不支持分布式。Spring Task 并不是为分布式环境设计的,在分布式环境下,这种定时任务是不支持集群配置的,如果部署到多个节点上,各个节点之间并没有任何协调通讯机制,集群的节点之间是不会共享任务信息的,每个节点上的任务都会按时执行,导致任务的重复执行。我们可以使用支持分布式的定时任务调度框架,比如 Quartz、XXL-Job、Elastic Job。当然你可以借助 zookeeper 、 redis 等实现分布式锁来处理各个节点的协调问题。或者把所有的定时任务抽成单独的服务单独部署。

基于 Redis 的分布式实现

https://blog.csdn.net/weixin_36380516/article/details/123320936

动态定时任务的实现

https://zhuanlan.zhihu.com/p/61526583

4.Quartz

Quartz的基本配置

哑火

group 的作用

核心操作

方法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值