Java服务端开发4:使用定时任务

前言:Java开发过程中经常会遇到使用定时任务的情况,比如在某个活动结束时,自动生成获奖名单,导出excel等。常见的有如下四种方式:Timer、ScheduledExecutorService、SpringTask、Quartz。

Java定时任务的四种实现方式

(1)JDK 自带的定时器实现

(2)Quartz 定时器实现 

(3)Spring Task相关的任务调度

- JDK自带 :JDK自带的Timer以及JDK1.5+ 新增的ScheduledExecutorService; 
- Quartz :简单却强大的JAVA作业调度框架;
- Spring3.0以后自带的task调度工具 :可以将它看成一个轻量级的Quartz,而且使用起来比Quartz简单许多; 

1、JDK自带的java.util.Timer 来实现

import java.util.Calendar;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
 
 
public class Test {
    /**
     *  第一种方法:设定指定任务task在指定时间执行,只执行一次  
     *  schedule(TimerTask task, Date time)  
     */
    public static void timer1() {  
        Timer timer = new Timer();  
        timer.schedule(new TimerTask() {  
            public void run() {  
                System.out.println(new Date() +"\t"+"---指定要执行任务---");  
            }  
        },  new Date(System.currentTimeMillis() + 2000)); 
    }  
    
    /**
     *  第二种方法:设定指定任务task在延迟delay执行,只执行一次    
     *  schedule(TimerTask task, long delay)   
     *  delay单位毫秒
     */
    public static void timer2(){
        Timer timer = new Timer();  
        timer.schedule(new TimerTask() {  
            public void run() {  
                System.out.println(new Date() +"\t"+"---指定要执行任务---");  
            }  
        }, 2000);  
    }
    
    /**
     *  第三种方法:设定指定任务task在指定延迟delay后进行周期性执行,周期时间为period  
     *  schedule(TimerTask task, long delay, long period) 
     *  scheduleAtFixedRate(TimerTask task, long delay, long period)  
     *  delay,period 单位为毫秒
     */
    public static void timer3() {  
        Timer timer = new Timer();  
        timer.schedule(new TimerTask() {  
            public void run() {  
                System.out.println(new Date() +"\t"+"---指定要执行任务---");  
            }  
        }, 1000, 1000);  
    }  
    /**
     *  第四种方法:设定指定的任务task在指定的时间firstTime开始进行重复的周期性执行,周期时间为period
     *  schedule(TimerTask task, Date firstTime, long period) 
     *  scheduleAtFixedRate(TimerTask task,Date firstTime,long period)
     *  period 单位为毫秒
     */
    public static void timer4() {  
        Calendar calendar = Calendar.getInstance();  
        calendar.set(Calendar.HOUR_OF_DAY, 12); // 控制时  
        calendar.set(Calendar.MINUTE, 0);       // 控制分  
        calendar.set(Calendar.SECOND, 0);       // 控制秒  
  
        Date time = calendar.getTime();         // 得出执行任务的时间,此处为今天的12:00:00  
        Timer timer = new Timer();  
        timer.schedule(new TimerTask() {  
            public void run() {  
                System.out.println(new Date() +"\t"+"---指定要执行任务---");  
            }  
        }, time, 1000);
    }  
    
    /**
     * schedule和scheduleAtFixedRate方法的区别:
     * (1)schedule方法:如果第一次执行时间被delay了,随后的执行时间按照上一次实际执行完成的时间点进行计算,即:下一次的执行时间点=上一次程序执行完成的时间点+间隔时间 
     * (2)scheduleAtFixedRate方法:如果第一次执行时间被delay了,随后的执行时间按照上一次开始的时间点进行计算,即:下一次的执行时间点=上一次程序开始执行的时间点+间隔时间,
     * 并且前一个任务的执行时间大于间隔时间,就会与当前任务重叠,TimerTask中的执行体需要考虑线程同步 
     */
    
}

Timer的缺陷:

(1)由于执行任务的线程只有一个,所以如果某个任务的执行时间过长,那么将破坏其他任务的定时精确性。如一个任务每1秒执行一次,而另一个任务执行一次需要5秒,那么如果是固定速率的任务,那么会在5秒这个任务执行完成后连续执行5次,而固定延迟的任务将丢失4次执行。

(2)如果执行某个任务过程中抛出了异常,那么执行线程将会终止,导致Timer中的其他任务也不能再执行。

(3)Timer使用的是绝对时间,即是某个时间点,所以它执行依赖系统的时间,如果系统时间修改了的话,将导致任务可能不会被执行。

由于Timer存在上面说的这些缺陷,在JDK1.5中,我们可以使用ScheduledThreadPoolExecutor来代替它,使用Executors.newScheduledThreadPool工厂方法或使用ScheduledThreadPoolExecutor的构造函数来创建定时任务,它是基于线程池的实现,不会存在Timer存在的上述问题,当线程数量为1时,它相当于Timer。

2、JDK自带的ScheduledThreadPoolExecutor来实现

 /**
     * ScheduledThreadPoolExecutor 有两种创建方式:第一种,通过Executors创建;第二种:通过构造方法
     * scheduleAtFixedRate(Runnable command, long initialDelay, long period,TimeUnit unit)
     * initialDelay 首次执行任务的延迟时间
     * period  每次执行任务的间隔时间
     * unit   执行的时间间隔数值单位
     * 间隔单位毫秒:TimeUnit.MILLISECONDS 
     * 间隔单位秒:TimeUnit.SECONDS 
     * 间隔单位分钟:TimeUnit.MINUTES 
     * 间隔单位小时:TimeUnit.HOURS 
     * 间隔单位天:TimeUnit.DAYS
     */
    public static void timer5(){
        ScheduledThreadPoolExecutor scheduled = (ScheduledThreadPoolExecutor)Executors.newScheduledThreadPool(10);
//        ScheduledThreadPoolExecutor  scheduled = new ScheduledThreadPoolExecutor(10);
        scheduled.scheduleAtFixedRate(new Runnable() {
             @Override
             public void run() {
                 System.out.println(new Date());
             }
         }, 0, 40, TimeUnit.MILLISECONDS);
    }

3、Quartz整合spring实现

(1)增加maven依赖

<dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-web</artifactId>
   <version>3.2.9.RELEASE</version>    
</dependency>
<dependency>
   <groupId>org.quartz-scheduler</groupId>
   <artifactId>quartz</artifactId>
   <version>1.8.5</version>
</dependency>

(2)增加定时业务类

package quartz;
 
import java.util.Date;
 
import javax.annotation.PostConstruct;
 
import org.springframework.stereotype.Service;
 
@Service("ExpireJobTaskService")
public class ExpireJobTask {
    
    public void reload() {
        load();
    }
    
    @PostConstruct
    public void load(){
        System.out.println(new Date()+"\t"+"执行定时任务");
    }
 
}

(3)增加spring配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:task="http://www.springframework.org/schema/task"
    xsi:schemaLocation="
      http://www.springframework.org/schema/context classpath:org/springframework/context/config/spring-context-3.0.xsd
      http://www.springframework.org/schema/beans classpath:org/springframework/beans/factory/xml/spring-beans-3.0.xsd
      http://www.springframework.org/schema/aop classpath:org/springframework/aop/config/spring-aop-3.0.xsd">
 
    
    <!-- 扫描指定package下所有组件注解并注册为Spring Beans -->
    <context:component-scan base-package="quartz" />
    
    
    <!-- 调度业务 -->
    <bean id="ExpireJobTaskJobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
        <property name="targetObject" ref="ExpireJobTaskService" />
        <property name="targetMethod" value="reload" />
    </bean>
    <!-- 调度触发器 -->
    <bean id="ExpireJobTaskJobTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
        <property name="jobDetail" ref="ExpireJobTaskJobDetail" />
        <property name="cronExpression" value="0 0/1 * * * ?" />
    </bean> 
    
    <!-- 设置调度    triggers属性中,我们可以增加多个触发器。 -->
    <bean name="startQuertz"  class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <property name="triggers">
            <list>
                <ref bean="ExpireJobTaskJobTrigger" />
            </list>
        </property>
    </bean>
    
    
</beans>

Quartz 主要包含以下几个部分:

Job:是一个接口,只有一个方法void execute(JobExecutionContext

context,开发者实现该接口定义运行任务,JobExecutionContext类提供了调度上下文的各种信息。Job运行时的信息保存在JobDataMap实例中;

JobDetail:Quartz在每次执行Job时,都重新创建一个Job实例,所以它不直接接受一个Job的实例,相反它接收一个Job实现类,以便运行时通过newInstance()的反射机制实例化Job。因此需要通过一个类来描述Job的实现类及其它相关的静态信息,如Job名字、描述、关联监听器等信息,JobDetail承担了这一角色。

Trigger:是一个类,描述触发Job执行的时间触发规则。主要有SimpleTrigger和CronTrigger这两个子类。当仅需触发一次或者以固定时间间隔周期执行,SimpleTrigger是最适合的选择;而CronTrigger则可以通过Cron表达式定义出各种复杂时间规则的调度方案:如每早晨9:00执行,周一、周三、周五下午5:00执行等;

Calendar:org.quartz.Calendar和java.util.Calendar不同,它是一些日历特定时间点的集合(可以简单地将org.quartz.Calendar看作java.util.Calendar的集合——java.util.Calendar代表一个日历时间点,无特殊说明后面的Calendar即指org.quartz.Calendar)。一个Trigger可以和多个Calendar关联,以便排除或包含某些时间点。假设,我们安排每周星期一早上10:00执行任务,但是如果碰到法定的节日,任务则不执行,这时就需要在Trigger触发机制的基础上使用Calendar进行定点排除。

Scheduler:代表一个Quartz的独立运行容器,Trigger和JobDetail可以注册到Scheduler中,两者在Scheduler中拥有各自的组及名称,组及名称是Scheduler查找定位容器中某一对象的依据,Trigger的组及名称必须唯一,JobDetail的组和名称也必须唯一(但可以和Trigger的组和名称相同,因为它们是不同类型的)。Scheduler定义了多个接口方法,允许外部通过组及名称访问和控制容器中Trigger和JobDetail。

Scheduler可以将Trigger绑定到某一JobDetail中,这样当Trigger触发时,对应的Job就被执行。一个Job可以对应多个Trigger,但一个Trigger只能对应一个Job。可以通过SchedulerFactory创建一个Scheduler实例。Scheduler拥有一个SchedulerContext,它类似于ServletContext,保存着Scheduler上下文信息,Job和Trigger都可以访问SchedulerContext内的信息。SchedulerContext内部通过一个Map,以键值对的方式维护这些上下文数据,SchedulerContext为保存和获取数据提供了多个put()和getXxx()的方法。可以通过Scheduler#

getContext()获取对应的SchedulerContext实例;

ThreadPool:Scheduler使用一个线程池作为任务运行的基础设施,任务通过共享线程池中的线程提高运行效率。

4、spring定时任务实现

案例:定期清理未激活用户,节省资源,方便用户再次注册。

Spring 3.0+ 自带的任务调度实现,主要依靠TaskScheduler接口的几个实现类实现。删除和修改任务比较麻烦。 
主要用法有以下三种: 

  • Spring配置文件实现

  • 注解实现

  • 代码动态添加

Spring定时任务比较简单,只需要在maven依赖中引入相关jar包,这里使用注解进行配置。

示例:

Spring @Scheduled注解执行定时任务

package quartz;
 
import java.util.Date;
 
import javax.annotation.PostConstruct;
 
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
 
@Service
public class ExpireJobTask {
    
    @PostConstruct
    public void reload() {
        load();
    }
    
    @Scheduled(cron = "0 0/1 0 * * ?")
    public void load(){
        System.out.println(new Date()+"\t"+"执行定时任务");
    }
 
}

参考链接:

Java定时任务

SpringTask

@Scheduled cron表达式

@Schedule cron表达式 时彬斌

猜你喜欢

转载自blog.csdn.net/CSDN2497242041/article/details/115130334