几种常用定时任务的使用

生活中,最使人疲惫的往往不是道路的遥远,而是心中的郁闷;最使人痛苦的往往不是生活的不幸,而是希望的破灭;最使人颓废的往往不是前途的坎坷,而是自信的丧失;最使人绝望的往往不是挫折的打击,而是心灵的死亡。所以我们要有自己的梦想,让梦想的星光指引着我们走出落漠,走出惆怅,带着我们走进自己的理想。

导读:本篇文章讲解 几种常用定时任务的使用,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

一、实现定时任务的方案

1 、使用Thread.sleep(1000)

2 、使用jdk的Timer和TimerTask,可以实现简单的间隔执行任务(在指定时间点执行某一任务,也能定时的周期性执行),无法实现按日历去调度执行任务。

3、使用DelayQueue队列

4、使用Quartz,它是一个异步任务调度框架,功能丰富,可以实现按日历调度。

5、使用Spring Task,Spring 3.0后提供Spring Task实现任务调度,支持按日历调度,相比Quartz功能稍简单,但是在开发基本够用,支持注解编程方式。
	
7、分布式定时任务之XXL-JOB

二、Thread方式

    public static void main(String[] args) {

		final int[] count = {1};

        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("第 " + count[0]++ + " 次执行...");
                }
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
    }

三、Timer和TimerTask实现

Timer与TimerTask

    public static void main(String[] args) {
        final long[] count = {0};

//		TimerTask timerTask1=new TimerTask() {
//			@Override
//			public void run() {
//				count[0]++;
//				System.out.println("第 " + count[0] + " 次执行...");
//			}
//		};

        TimerTask timerTask2 = new TimerTask() {

            @Override
            public void run() {
                count[0]++;
                System.out.println("第 " + count[0] + " 次执行...");
            }
        };

        Timer timer = new Timer();
        // 天
        long delay = 0;
        // 秒
        long period = 1000;
        timer.scheduleAtFixedRate(timerTask2, delay, period);
    }

在这里插入图片描述

schedule(2 args) : 指定的计划执行时间 <= 当前时间,则task会被立即执行。

schedule(3 args) : 指定的计划执行时间 <= 当前时间,则task会被立即执行,之后按period参数固定重复执行。

scheduleAtFixedRate(3 args):指定的计划执行时间 <= 当前时间,则task会首先按执行一次;然后按照执行时间、系统当前时间和period参数计算出过期该执行的次数,计算按照: (当前时间 – 计划执行时间)/period,再次执行计算出的次数;最后按period参数固定重复执行。

schedule()方法更注重保持间隔时间的稳定。 scheduleAtFixedRate()方法更注重保持执行频率的稳定。

ScheduledExecutorService

阿里编码规约:

Timer运行多个TimeTask时,只要其中之一没有捕获抛出的异常,其它任务便会自动终止运行。Timer 优化方案,使用ScheduledExecutorService

ScheduledExecutorService是并发工具包中的类,是最理想的定时任务实现方式。

	public static void main(String[] args) {
		Runnable runnable1 = new Runnable() {
			@Override
			public void run() {
				System.out.println("Hello runnable1");
			}
		};

		Runnable runnable2 = new Runnable() {
			@Override
			public void run() {
				System.out.println("Hello runnable2");
			}
		};
		
		//方式一
		//定义4个线程
		ScheduledExecutorService service = Executors.newScheduledThreadPool(4);
		/**
		 * 参数1:实现Runnable接口的类
		 * 参数2:启动等待时间,首次执行的延时时间
		 * 参数3:一个数值,定时执行的间隔时间
		 * 参数4:与参数3对应的单位
		 */
		 ScheduledFuture<?> scheduledFuture= service.scheduleAtFixedRate(runnable1, 0,2, TimeUnit.SECONDS);

		//方式二
		ScheduledExecutorService service2 = Executors.newSingleThreadScheduledExecutor();
		service2.scheduleAtFixedRate(runnable2, 1, 2, TimeUnit.SECONDS);
	}

在这里插入图片描述

scheduleAtFixedRate() 按照固定频率

scheduleWithFixedDelay() 如果任务时间超过固定频率,按照任务实际时间延后

四、DelayQueue

DelayQueue是一个支持延时获取元素的阻塞队列, 内部采用优先队列PriorityQueue存储元素,同时元素必须实现Delayed 接口;在创建元素时可以指定多久才可以从队列中获取当前元素,只有在延迟期满时才能从队列中提取元素。

DelayQueue属于排序队列,它的特殊之处在于队列的元素必须实现Delayed接口,该接口需要实现
compareTo和getDelay方法

getDelay方法:获取元素在队列中的剩余时间,只有当剩余时间为0时元素才可以出队列。

compareTo方法:用于排序,确定元素出队列的顺序。

poll():获取并移除队列的超时元素,没有则返回空。

take():获取并移除队列的超时元素,如果没有则wait当前线程,直到有元素满足超时条件,返回结果。
public class DelayedTask implements Delayed {

    // 任务的执行时间
    private int executeTime = 0;

    public DelayedTask(int delay) {
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.SECOND, delay);
        this.executeTime = (int) (calendar.getTimeInMillis() / 1000);
    }

    /**
     * 元素在队列中的剩余时间
     *
     * @param unit
     * @return
     */
    @Override
    public long getDelay(TimeUnit unit) {
        Calendar calendar = Calendar.getInstance();
        return executeTime - (calendar.getTimeInMillis() / 1000);
    }

	/**
     * 元素排序
     *
     * @param o
     * @return
     */
    @Override
    public int compareTo(Delayed o) {
        long delay = this.getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS);
        return delay == 0 ? 0 : (delay < 0 ? -1 : 1);
    }

    public static void main(String[] args) {
    	// 添加三个延迟任务(任务都是放在内存中)
        DelayQueue<DelayedTask> queue = new DelayQueue<>();
        queue.add(new DelayedTask(5));
        queue.add(new DelayedTask(10));
        queue.add(new DelayedTask(15));

        System.out.println("start: " + System.currentTimeMillis() / 1000);

		// 循环 从延迟队列中拉取任务
        while (queue.size() != 0) {
            DelayedTask delayedTask = queue.poll();
            if (delayedTask != null) {
                System.out.println("execute task: " + System.currentTimeMillis() / 1000);
            }

            //每隔2秒消费一次
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

五、Quartz实现

引入依赖

	<dependency>
			<groupId>org.quartz-scheduler</groupId>
			<artifactId>quartz</artifactId>
			<version>2.3.2</version>
		</dependency>
		<dependency>
			<groupId>org.quartz-scheduler</groupId>
			<artifactId>quartz-jobs</artifactId>
			<version>2.3.2</version>
		</dependency>

新建Job任务

public class MyJob implements Job {

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        System.out.println("hello quartz");
    }
}

任务调度类

    public static void main(String[] args) throws SchedulerException {
        //创建Scheduler的工厂
        SchedulerFactory sf = new StdSchedulerFactory();
        //从工厂中获取调度器实例
        Scheduler scheduler = sf.getScheduler();

        /**
         * 创建JobDetail
         * withDescription:job的描述
         * withIdentity:job的name和group
         */
        JobDetail jb = JobBuilder.newJob(MyJob.class)
                .withDescription("this is a job")
                .withIdentity("myJob", "myJobGroup")
                .build();

        //任务运行的时间,5秒后启动任务
        long time = System.currentTimeMillis() + 5 * 1000L;
        Date statTime = new Date(time);

        //创建Trigger,使用SimpleScheduleBuilder或者CronScheduleBuilder
        Trigger t = TriggerBuilder.newTrigger()
                .withDescription("this is a trigger")
                .withIdentity("myTrigger", "myTriggerGroup")
                //.withSchedule(SimpleScheduleBuilder.simpleSchedule())
                //设置启动时间
                .startAt(statTime)
                //每隔3秒执行一次
                .withSchedule(CronScheduleBuilder.cronSchedule("0/3 * * * * ? *"))
                .build();

        //注册任务和定时器
        scheduler.scheduleJob(jb, t);

        //启动 调度器
        scheduler.start();
    }

六、Cron表达式

Cron表达式是一个字符串,字符串以5或6个空格隔开,分为6或7个域,每一个域代表一个含义。从左至右,这些元素的定义如下:

1.秒(0–59)
2.分钟(0–59)
3.小时(0–23)
4.月份中的日期(1–31)
5.月份(1–12 或 JAN–DEC)
6.星期中的日期(1–7 或 SUN–SAT)
7.年份(1970–2099)

表达式的格式

Cron表达式的格式 : 秒 分 时 日 月 周 年(可选)

1.Seconds
2.Minutes
3.Hours
4.Day-of-Month
5.Month
6.Day-of-Week
7.Year(可选)

Seconds Minutes Hours DayofMonth Month DayofWeek Year 

Seconds Minutes Hours DayofMonth Month DayofWeek

cron 表达式包括6/7部分:

Seconds 秒(0~59) :可出现 ,- * / 四个字符,有效范围为0-59的整数

Minutes 分钟(0~59):可出现 ,- * / 四个字符,有效范围为0-59的整数

Hours 小时(0~23):可出现 ,- * / 四个字符,有效范围为0-23的整数

DayofMonth 月中的天(1~31):可出现 ,- * / ? L W C八个字符,有效范围为1-31的整数

Month 月(1~12) :可出现 ,- * / 四个字符,有效范围为1-12的整数或JAN-DEc

DayofWeek 周中的天(MON,TUE,WED,THU,FRI,SAT,SUN,或数字1~7 1表示MON,依次类推):可出现  ,- * / ? L C #四个字符,有效范围为1-7的整数或SUN-SAT两个范围。1表示星期天,2表示星期一, 依次类推

Year 年(1970-8099)::可出现 ,- * / 四个字符,有效范围为1970-2099年

表达式的特殊字符

/  表示指定数值的增量,从起始时间开始触发,然后每隔固定时间触发一次
	如在Minutes域使用5/10,则意味着5分钟触发一次,而15,25等分别触发一次.

* 表示匹配该域的任意值,如在Minutes域使用*,即表示每分钟都会触发事件。

-  表示区间范围,如在Minutes域使用5-10,表示从5分到10分钟每分钟触发一次

,  表示列出枚举值,如在Minutes域使用5,10,则意味着在5和10分钟触发一次。

? 表示不指定值,只能用在月中的天(DayofMonth)和周中的天(DayofWeek)两个域,DayofMonth和DayofWeek会相互影响。
	如每月1日触发,不管1日是星期几,只能这样写: * * * 1 * ?,最后一位只能用?不能使用*,如果使用*表示不管星期几都会触发,实际上并不是这样。

L 表示 day-of-month和day-of-week域,但在两个字段中意思不同,在day-of-month域中表示:一个月的最后一天 在day-of-week域中表示"7"或者"SAT",如果在day-of-week域前加上数字,表示一个月的最后几天,如:"6L"表示一个月的最后一个星期五

W  只允许日期域出现,用于指定日期的最近工作日.如:在日期域中写"15W",表示这个月15号最近的工作日,所以如果15号是周六则任务会在14号触发,如果15号是周日,则任务会在周一也就是16号触发.如果日期域填写"1W"即使1号是周六,那么任务也只会在下周一,也就是3号触发,"W"字符指定最近工作日是不能够跨月的,字符"W"只能配合一个单独的数值使用不能是一个数字段,如1-12W是错误的

LW L和W可以在日期域中联合使用 LW表示这个月最后一周的工作日

# 只允许在星期域中出现 这个字符用于指定本月的某某天 如:"6#3"表示本月第三周的星期五(6表示星期五,3表示周) "2#1"表示本月第一周的星期一 "4#5"表示第五周的星期三

C  允许在日期域和星期域出现 这字符依靠一个指定的"日历" 也就是说这个表达式的值依赖于相关的"日历"的计算结果 如果没有"日历"关联 ,则等价于所有包含"日历" 如: 日期域是5C表示关联"日历"中第一天,或者这个月开始的第一天的后5天,星期域是"1C"表示关联"日历""中第一天,或者星期的第一天的后1天,也就是周日的后一天(周一)	

表达式的示例

表达式 含义
0/2 * * * * * 每隔2秒执行
0 0/2 * * * * 每隔2分钟执行
0 0 0 * * * 表示每天0点执行
0 0 12 ? * WEN 每周三12点执行
0 20 8 ? * MON-FRI 每月的周一到周五8点 20分执行
0 20 8 ? * MON,FRI 每月的周一和周五8点 20分执行
0 0 10,14,16 * * ? 每天上午10点,下午2点,4点
0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时
0 0 12 ? * WED 表示每个星期三中午12点
0 0 12 * * ? 每天中午12点触发
0 15 10 ? * * 每天上午10:15触发
0 15 10 * * ? 每天上午10:15触发
0 15 10 * * ? * 每天上午10:15触发
0 15 10 * * ? 2022 2022年的每天上午10:15触发
0 * 14 * * ? 在每天下午2点到下午2:59期间的每1分钟触发
0 0/5 14 * * ? 在每天下午2点到下午2:55期间的每5分钟触发
0 0/5 14,17 * * ? 在每天下午2点到2:55期间和下午5点到5:55期间的每5分钟触发
0 0-5 14 * * ? 在每天下午2点到下午2:05期间的每1分钟触发
0 10,44 14 ? 3 WED 每年三月的星期三的下午2:10和2:44触发
0 15 10 ? * MON-FRI 周一至周五的上午10:15触发
0 15 10 15 * ? 每月15日上午10:15触发
0 15 10 L * ? 每月最后一日的上午10:15触发
0 15 10 ? * 6L 每月的最后一个星期五上午10:15触发
0 15 10 ? * 6L 2020-2022 2020年至2022年的每月的最后一个星期五上午10:15触发
0 15 10 ? * 6#3 每月的第三个星期五上午10:15触发

七、Spring Task实现定时任务

SpringTask使用Cron表达式时,只能写6个域!

XML配置实现

1.添加依赖

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-context</artifactId>
	<version>5.2.4.RELEASE</version>
</dependency>

2.定义定时任务
新建类,添加自动注入的注解,定义定义任务的方法

@Component
public class TaskJob {

	public void job1(){
		System.out.println("任务 1:" + new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));
	}
	
	public void job2(){
		System.out.println("任务 2:" + new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));
	}
}

3. 配置文件spring.xml
src/main/resources目录下新建配置文件spring.xml,并设置Spring扫描

<beans xmlns="http://www.springframework.org/schema/beans"
		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		xmlns:context="http://www.springframework.org/schema/context"
		xmlns:task="http://www.springframework.org/schema/task"
		xsi:schemaLocation="http://www.springframework.org/schema/beans
		https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context
		http://www.springframework.org/schema/context/spring-context.xsd
		http://www.springframework.org/schema/task
		http://www.springframework.org/schema/task/spring-task.xsd">
		
	<!-- Spring扫描注解配置 -->
	<context:component-scan base-package="cn.ybzy.demo" />

	<!--配置定时规则: ref:指定任务类;method:指定运行的方法;cron:cronExpression表达式 -->
	<task:scheduled-tasks>
		<!-- 每个两秒执行一次任务 -->
		<task:scheduled ref="taskJob" method="job1" cron="0/2 * * * * ?"/>
		<!-- 每隔五秒执行一次任务 -->
		<task:scheduled ref="taskJob" method="job2" cron="0/5 * * * * ?"/>
	</task:scheduled-tasks>

</beans>

4.测试定时任务

public static void main( String[] args ) {
	// 获取Spring上下文环境
	ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
	// 获取指定Bean对象
	TaskJob taskJob = (TaskJob) ac.getBean("taskJob");
}

注解配置实现

1.定义定时任务

@Component
public class TaskJob02 {

	@Scheduled(cron="0/2 * * * * ?")
	public void job1(){
		System.out.println("任务 1:" + new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));
	}
	
	@Scheduled(cron="0/5 * * * * ?")
	public void job2(){
		System.out.println("任务 2:" + new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));
	}
}

2.配置定时任务驱动

<beans xmlns="http://www.springframework.org/schema/beans"
		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		xmlns:context="http://www.springframework.org/schema/context"
		xmlns:task="http://www.springframework.org/schema/task"
		xsi:schemaLocation="http://www.springframework.org/schema/beans
		https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context
		http://www.springframework.org/schema/context/spring-context.xsd
		http://www.springframework.org/schema/task
		http://www.springframework.org/schema/task/spring-task.xsd">
		
	<!-- Spring扫描注解配置 -->
	<context:component-scan base-package="cn.ybzy.demo" />

	<!-- 配置定时任务驱动。开启这个配置,spring才能识别@Scheduled注解 -->
	<task:annotation-driven />
</beans>

3.测试定时任务

public static void main( String[] args ) {
	// 获取Spring上下文环境
	ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
	// 获取指定Bean对象
	TaskJob02 taskJob02 = (TaskJob02) ac.getBean("taskJob02");
}

Spring Boot

在Spring boot启动类上添加注解:@EnableScheduling

@Component
public class SpringTaskTest {
    
    private static final Logger LOGGER = LoggerFactory.getLogger(SpringTaskTest.class);

   /**
     * 每隔2秒执行一次
     */
    @Scheduled(cron = "0/2 * * * * *")
    public void task1() {
        LOGGER.info("--------------------task1开始--------------------");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        LOGGER.info("--------------------task1结束--------------------");
    }
}

串行任务与并行任务

除了使用cron表达式,还支持如下用法

@Scheduled(fixedRate = 2000)  上次执行开始时间后2秒执行

@Scheduled(fixedDelay = 2000)   上次执行完毕后2秒执行

@Scheduled(initialDelay=2000, fixedRate=5000)   第一次延迟2秒,以后每隔5秒执行一次

1.串行任务

两个任务方法由一个线程串行执行,task1方法执行完成task2再执行。

	/**
     * 每隔2秒执行一次
     */
    @Scheduled(cron = "0/2 * * * * *")
    public void task1() {
        LOGGER.info("--------------------task1开始--------------------");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        LOGGER.info("--------------------task1结束--------------------");
    }

    /**
     * 上次执行开始时间后5秒执行
     */
    @Scheduled(fixedRate = 5000)
    public void task2(){
        LOGGER.info("--------------------task2开始--------------------");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        LOGGER.info("--------------------task2结束--------------------");
    }

两个任务交替执行,即任务串行执行.

 -------------------task2开始--------------------
--------------------task2结束--------------------
 -------------------task1开始--------------------
 -------------------task1结束--------------------
--------------------task2开始--------------------
--------------------task2结束--------------------
--------------------task1开始--------------------
 -------------------task1结束--------------------

2.并行任务

创建添加任务配置类,需要配置线程池实现多线程调度任务

@Configuration
//启动类 or 此处配置@EnableScheduling
public class TaskConfig implements SchedulingConfigurer, AsyncConfigurer {

    /**
     * 线程池线程数量
     */
    private int poolSize = 5;

    @Bean
    public ThreadPoolTaskScheduler taskScheduler() {
        //创建定时任务线程池
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        ///初始化线程池
        scheduler.initialize();
        //线程池容量
        scheduler.setPoolSize(poolSize);
        return scheduler;
    }

    @Override
    public Executor getAsyncExecutor() {
        Executor executor = taskScheduler();
        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return null;
    }

    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
        scheduledTaskRegistrar.setTaskScheduler(taskScheduler());
    }
}

添加任务配置类后,任务并行执行.

--------------------task2开始--------------------
 -------------------task1开始--------------------
 -------------------task2结束--------------------
--------------------task1结束--------------------
 -------------------task1开始--------------------
 -------------------task2开始--------------------
--------------------task1结束--------------------
--------------------task2结束--------------------

八、分布式定时任务之XXL-JOB

篇幅过长,参考:分布式定时任务之XXL-JOB

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/137064.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!