Java实现定时任务

 更新时间:2022年09月02日 16:25:12   作者:dreaming9420  
这篇文章主要为大家详细介绍了Java实现定时任务,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

本文实例为大家分享了Java实现定时任务的具体代码,供大家参考,具体内容如下

1 使用java.util.Timer

这种方式的定时任务主要用到两个类,Timer 和 TimerTask,使用起来比较简单。其中 Timer 负责设定 TimerTask 的起始与间隔执行时间。 TimerTask是一个抽象类,new的时候实现自己的 run 方法,然后将其丢给 Timer 去执行即可。

代码示例:

import java.time.LocalDateTime;
import java.util.Timer;
import java.util.TimerTask;

public class Schedule {
    public static void main(String[] args) {
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                System.out.println("当前线程:" + Thread.currentThread().getName() + " 当前时间" + LocalDateTime.now());
            }
        };
        // 在指定延迟0毫秒后开始,随后地执行以2000毫秒间隔执行timerTask 
        new Timer().schedule(timerTask, 0L, 2000L);
        System.out.println("当前线程:" + Thread.currentThread().getName() + " 当前时间" + LocalDateTime.now());
    }
}

缺点:

  • Timer 的背后只有一个线程,不管有多少个任务,都只有一个工作线程串行执行,效率低下
  • 受限于单线程,如果第一个任务逻辑上死循环了,后续的任务一个都得不到执行
  • 依然是由于单线程,任一任务抛出异常后,整个 Timer 就会结束,后续任务全部都无法执行

2 使用ScheduledExecutorService

ScheduledExecutorService 即是 Timer 的替代者,JDK 1.5 并发包引入,是基于线程池设计的定时任务类。每个调度任务都会分配到线程池中的某一个线程去执行,任务就是并发调度执行的,任务之间互不影响。

Java 5.0引入了java.util.concurrent包,其中的并发实用程序之一是ScheduledThreadPoolExecutor ,它是一个线程池,用于以给定的速率或延迟重复执行任务。它实际上是Timer/TimerTask组合的更通用替代品,因为它允许多个服务线程,接受各种时间单位,并且不需要子类TimerTask (只需实现Runnable)。使用一个线程配置ScheduledThreadPoolExecutor使其等效于Timer 。

代码示例:

import java.time.LocalDateTime;
import java.util.concurrent.*;

public class Schedule {
    public static void main(String[] args) {
        // 创建一个ScheduledThreadPoolExecutor线程池,核心线程数为5
        ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(5);
        // 创建Runnable打印当前线程和当前时间
        Runnable r = () -> System.out.println("当前线程:" + Thread.currentThread().getName() + " 当前时间" + LocalDateTime.now());
        /**
         * schedule:只执行一次调度
         * scheduleAtFixedRate:一开始就计算间隔时间,如果任务超过间隔时间,那么就直接开始下一个任务
         * scheduleWithFixedDelay:任务无论执行多久,都要等待上一轮任务完成之后再间隔指定时间,然后才开始下一个任务
         */
         // 在指定1秒延迟后执行r,之后每两秒执行一次
        scheduledExecutorService.scheduleAtFixedRate(r, 1, 2, TimeUnit.SECONDS);
    }
}

3 使用Spring Task

Spring Task 底层是基于 JDK 的 ScheduledThreadPoolExecutor 线程池来实现的。直接通过Spring 提供的 @Scheduled 注解即可定义定时任务,非常方便。

以Spring Boot来作为示例,步骤为

1.在启动类所在包下创建Schedule 类(在没有配置@ComponentScan的情况下,Spring Boot只会默认扫描启动类所在包的spring组件)
2.在该类上添加@Component和@EnableScheduling注解
3.在方法上添加@Scheduled注解,该注解主要参数如下

String cron() default "";  // 支持cron表达式

long fixedDelay() default -1;  // 在最后一次调用结束和下一次调用开始之间的时间间隔,以毫秒为单位
String fixedDelayString() default "";  // 同上,类似ScheduledExecutorService的scheduleWithFixedDelay

long fixedRate() default -1;  // 在调用之前的时间间隔,以毫秒为单位
String fixedRateString() default "";  // 同上,类似ScheduledExecutorService的scheduleAtFixedRate

long initialDelay() default -1;  // 在第一次执行fixedRate()或fixedDelay()任务之前要延迟的毫秒数
String initialDelayString() default "";  // 同上

代码示例:

import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;

@Component
@EnableScheduling
public class Schedule {
    @Scheduled(fixedRate = 2000L)
    public void task() {
        System.out.println("当前线程:" + Thread.currentThread().getName() + " 当前时间" + LocalDateTime.now());
    }
}

  • 优点: 简单,轻量,支持 Cron 表达式
  • 缺点 :默认只支持单机,是单线程的,并且提供的功能比较单一

可以通过@EnableAsync和 @Async开启多线程

import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;

@Component
@EnableAsync  // 开启异步多线程
@EnableScheduling
public class Schedule {

    @Async
    @Scheduled(fixedRate = 2000L)
    public void task() {
        System.out.println("当前线程:" + Thread.currentThread().getName() + " 当前时间" + LocalDateTime.now());
    }
}

使用@EnableAsync注解后,默认情况下,Spring将搜索关联的线程池定义:上下文中的唯一org.springframework.core.task.TaskExecutor
的bean,或者名为“taskExecutor”的java.util.concurrent.Executor
的bean。如果两者都无法解析,则将使用org.springframework.core.task.SimpleAsyncTaskExecutor来处理异步方法调用。

TaskExecutor实现为每个任务启动一个新线程,异步执行它。 支持通过“concurrencyLimit”bean 属性限制并发线程。默认情况下,并发线程数是无限的,所以使用默认的线程池有导致内存溢出的风险。

注意:刚才的运行结果看起来是线程复用的,而实际上此实现不重用线程!应尽量实现一个线程池TaskExecutor ,特别是用于执行大量短期任务。不要使用默认的SimpleAsyncTaskExecutor。

import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.concurrent.Executor;

@Component
@EnableAsync
@EnableScheduling
public class Schedule {

    @Async
    @Scheduled(fixedRate = 2000L)
    public void task() {
        System.out.println("当前线程:" + Thread.currentThread().getName() + " 当前时间" + LocalDateTime.now());
    }


    @Bean("taskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(10);
        taskExecutor.setMaxPoolSize(50);
        taskExecutor.setQueueCapacity(200);
        taskExecutor.setKeepAliveSeconds(60);
        taskExecutor.setThreadNamePrefix("自定义-");
        taskExecutor.setAwaitTerminationSeconds(60);
        return taskExecutor;
    }
}

上面提到的一些定时任务的解决方案都是在单机下执行的,适用于比较简单的定时任务场景比如每天凌晨备份一次数据。如果我们需要一些高级特性比如支持任务在分布式场景下的分片和高可用的话,我们就需要用到分布式任务调度框架了,比如Quartz、Elastic-Job、XXL-JOB、PowerJob,本文就不再详细进行介绍了,感兴趣的可以自行查阅相关资料。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • 基于java web获取网页访问次数代码实例

    基于java web获取网页访问次数代码实例

    这篇文章主要介绍了基于java web获取网页访问次数代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-02-02
  • 使用SpringBoot进行身份验证和授权的示例详解

    使用SpringBoot进行身份验证和授权的示例详解

    在广阔的 Web 开发世界中,身份验证是每个数字领域的守护者,在本教程中,我们将了解如何以本机方式保护、验证和授权 Spring-Boot 应用程序的用户,并遵循框架的良好实践,希望对大家有所帮助
    2023-11-11
  • SpringMVC 上传文件 MultipartFile 转为 File的方法

    SpringMVC 上传文件 MultipartFile 转为 File的方法

    这篇文章主要介绍了SpringMVC 上传文件 MultipartFile 转为 File的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-02-02
  • SpringBoot异常: nested exception is java.lang.NoClassDefFoundError: javax/servlet/ServletContext解决方案

    SpringBoot异常: nested exception is java.lang.NoClassDefFoundE

    这篇文章主要介绍了SpringBoot异常: nested exception is java.lang.NoClassDefFoundError: javax/servlet/ServletContext解决方案,说明了错误原因和解决方案,需要的朋友可以参考下
    2021-06-06
  • 详解Java利用深度优先遍历解决迷宫问题

    详解Java利用深度优先遍历解决迷宫问题

    深度优先遍历:深度优先遍历是图论中的经典算法。其利用了深度优先搜索算法可以产生目标图的相应拓扑排序表,采用拓扑排序表可以解决很多相关的图论问题,如最大路径问题等等。本文将利用深度优先遍历解决迷宫问题,感兴趣的可以了解一下
    2022-02-02
  • JavaWeb学习笔记分享(必看篇)

    JavaWeb学习笔记分享(必看篇)

    下面小编就为大家带来一篇JavaWeb学习笔记分享(必看篇)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-06-06
  • 利用Java中Calendar计算两个日期之间的天数和周数

    利用Java中Calendar计算两个日期之间的天数和周数

    Java 语言的Calendar(日历),Date(日期),和DateFormat(日期格式)组成了Java标准的一个基本但是非常重要的部分。日期是商业逻辑计算一个关键的部分。下面这篇文章就给大家介绍了如何利用Java中Calendar计算两个日期之间的天数和周数,下面来一起看看吧。
    2016-12-12
  • Java基础之Math和Random类知识总结

    Java基础之Math和Random类知识总结

    今天带大家来学习java的Math和Random类,文中有非常详细的代码示例及介绍,对正在学习java基础的小伙伴们很有帮助哟,需要的朋友可以参考下
    2021-05-05
  • SpringBoot实现动态定时任务的示例代码

    SpringBoot实现动态定时任务的示例代码

    在SpringBoot项目中简单使用定时任务,不过由于要借助cron表达式且都提前定义好放在配置文件里,不能在项目运行中动态修改任务执行时间,实在不太灵活。现在我们就来实现可以动态修改cron表达式的定时任务,感兴趣的可以了解一下
    2022-10-10
  • SpringBoot Security使用MySQL实现验证与权限管理

    SpringBoot Security使用MySQL实现验证与权限管理

    安全管理是软件系统必不可少的的功能。根据经典的“墨菲定律”——凡是可能,总会发生。如果系统存在安全隐患,最终必然会出现问题,这篇文章主要介绍了SpringBoot安全管理Spring Security基本配置
    2022-11-11

最新评论