欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java定时任务调度

程序员文章站 2022-05-23 11:06:45
...

一、前言

在实际开发中,会经常定时更新一些数据,小到Java项目中的collection,大到数据库表格。又比如定时给客户发送email;又比如说交易所行情数据每天存储完成后,需要根据历史数据进行筛选新的开仓数据作为条件定时存储到其他数据库中。

二、Java自带的简单任务调用类

(一)、Timer

1、Timer实现任务调度的核心类是Timer和TimerTask

1)、创建一个TimerTask的子类,实现自己的run方法(自定义所要调度的任务);

2)、Timer负责设定TimerTask相对于当前多长时间执行此任务和执行任务的间隔。

2、原理:Timer将接受的任务丢到自己的TaskList中,TaskList按照Task的最初执行时间进行排列。

3、缺点:所有任务都是由一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务执行,前一个任务的延迟会影响到之后的任务。

4、简单代码:

public class TimerTest extends TimerTask {
	private String jobName="";
	public TimerTest(String jobName) {
		super();
		this.jobName=jobName;
	}
	@Override
	public void run() {
		System.out.println("excute :"+ jobName);
	}

	public static void main(String[] args) {
		Timer timer = new Timer();
		long delay1 = 1*1000;
		long peroid1 = 1000;
		// 从现在开始过1秒钟之后,每隔1秒钟执行一次job1(schedule:预定计划)
		timer.schedule(new TimerTest("job1"), delay1,peroid1);
		
		long delay2 = 2*1000;
		long peroid2 = 2000;
		// 从现在开始过2秒钟之后,每隔2秒钟执行一次job2
		timer.schedule(new TimerTest("-----job2"), delay2, peroid2);
	}
}

(二)、ScheduleExecutor任务安排器

1、鉴于Timer的缺陷,Java5推出了基于线程池设计的ScheduleExecutor.每个被调度的任务,都对应开启了一个线程,因此任务并发执行,相互之间不受影响;

2、当任务执行时间带来时候,任务安排器ScheduleExecutor才会真正的启动一个线程,其余时间ScheduleExecutor都在轮询任务状态。

3、代码:

public class ScheduleExecutor implements Runnable {
	private String jobName = "";
	public ScheduleExecutor(String jobName) {
		super();
		this.jobName = jobName;
	}
	@Override
	public void run() {
		System.out.println("execute :"+ jobName);
	}
	public static void main(String[] args) {
		ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
		long initialDelay1 = 1;
		long period1 = 1;
		// 从现在开始1秒之后,每隔1一秒钟执行一次job1
		service.scheduleAtFixedRate(new ScheduleExecutor("job1"), initialDelay1, period1, TimeUnit.SECONDS);
		
		long initialDelay2 = 2;
		long period2 = 2;
		// 从现在开始2秒之后,每隔2秒钟执行一次job2
		service.scheduleAtFixedRate(new ScheduleExecutor("----job2"), initialDelay2, period2, TimeUnit.SECONDS);
	}

}

三、开源工具包Quartz和JCrontab

(一)、Quartz

1、创建具体事件执行接口,Dothings.java,其两个子类DoingA.java、DoingB.java(只是打印两句话)

public static void doA() {
		SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		String date = format.format(new Date());
		System.out.println(date+"========="+"doing A");
	}
	public static void doB() {
		SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		String date = format.format(new Date());
		System.out.println(date+"***************"+"doing B");
	}
2、任务调用管理类QuartzManager.java

1)、添加任务、删除任务、修改任务(这里抽象成三个方法)

2)、每个方法封装调度器、任务名,组、触发器名,组、任务类、定时时间设置

3)、代码:

public class QuartzManager {
	private static SchedulerFactory schedulerFactory = new StdSchedulerFactory();

	/**
	 * 添加一个定时任务
	 * 
	 * @param jobName:任务名
	 * @param jobGroupName:任务组
	 * @param triggerName:触发器名
	 * @param triggerGroupName:触发器组
	 * @param jobClass:任务
	 * @param cron:时间设置
	 */
	public static void addJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName,
			Class jobClass, String cron) {
		try {
			// 创建一个scheduler
			Scheduler sched = schedulerFactory.getScheduler();
			// 创建一个JobDetail,指明name,groupname,以及具体的Job类名,该Job负责定义需要执行任务
			JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroupName).build();
			// 创建触发器对象
			TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger();
			// 触发器相关参数设计
			triggerBuilder.withIdentity(triggerName, triggerGroupName);
			triggerBuilder.startNow();
			// 触发器时间设定
			triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(cron));
			// 创建Trigger对象
			CronTrigger trigger = (CronTrigger) triggerBuilder.build();
			// 调度容器设置JobDetail和Trigger,将它们两关联起来。
			sched.scheduleJob(jobDetail, trigger);

			// 启动
			if (!sched.isShutdown()) {
				sched.start();
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	// 修改一个任务的触发时间
	public static void modifyJobTime(String jobName, String jobGroupName, String triggerName, String triggerGroupName,
			String cron) {
		try {
			Scheduler sched = schedulerFactory.getScheduler();
			TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);
			CronTrigger trigger = (CronTrigger) sched.getTrigger(triggerKey);
			if (trigger == null) {
				return;
			}

			String oldTime = trigger.getCronExpression();
			if (!oldTime.equalsIgnoreCase(cron)) {
				/** 方式一 :调用 rescheduleJob 开始 */
				// 触发器
				TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger();
				// 触发器名,触发器组
				triggerBuilder.withIdentity(triggerName, triggerGroupName);
				triggerBuilder.startNow();
				// 触发器时间设定
				triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(cron));
				// 创建Trigger对象
				trigger = (CronTrigger) triggerBuilder.build();
				// 方式一 :修改一个任务的触发时间
				sched.rescheduleJob(triggerKey, trigger);

				// 方式二:先删除,然后在创建一个新的Job
				// JobDetail jobDetail = sched.getJobDetail(JobKey.jobKey(jobName,
				// jobGroupName));
				// Class<? extends Job> jobClass = jobDetail.getJobClass();
				// removeJob(jobName, jobGroupName, triggerName, triggerGroupName);
				// addJob(jobName, jobGroupName, triggerName, triggerGroupName, jobClass, cron);
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	// 移除一个任务
	public static void removeJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName) {
		try {
			Scheduler sched = schedulerFactory.getScheduler();
			TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);
			sched.pauseTrigger(triggerKey);// 停止触发器
			sched.unscheduleJob(triggerKey);// 移除触发器
			sched.deleteJob(JobKey.jobKey(jobName, jobGroupName));// 删除任务
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}

3、创建Job的继承类,实现Execute方法

public class MyJob implements Job {
	@Override
	// 该方法实现需要执行的任务
	public void execute(JobExecutionContext arg0) throws JobExecutionException {
		DoingA.doA();
		DoingB.doB();
	}
}
4、测试TestDemo.java (周一到周五早上11:06:00秒执行DoingA和DoingB事件)

public class TestDemo {
	public static String JOB_NAME = "JOB_N";  
    public static String TRIGGER_NAME = "TRIGGER_N";  
    public static String JOB_GROUP_NAME = "JOB_GROUP_N";  
    public static String TRIGGER_GROUP_NAME = "TRIGGER_GROUP_N";
    
	public static void main(String[] args) {
		try {
			System.out.println("start two timed tasks"); 
			// 周一到周五早上11:06:00秒执行DoingA和DoingB事件(这里每个两秒中打印一次是为了测试)
            QuartzManager.addJob(JOB_NAME, JOB_GROUP_NAME, TRIGGER_NAME, TRIGGER_GROUP_NAME, MyJob.class, "0/2 06 11 ? * MON-FRI");  
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
}

5、测试结果

Java定时任务调度
6、总结扩展

1)、QuartzManager.java中:JobDetail负责封装Job以及Job属性,并将其提供给Scheduler作为参数,这里可以用JobMap来存储Job属性;

2)、QuartzManager.java中:Trigger设置调度策略,常用的是 SimpleTrigger 和 CronTrigger;

3)、除此之外,Quartz工具包还提供了Listener,主要包含三种 listener:JobListener,TriggerListener 以及 SchedulerListener。当系统发生故障,相关人员需要被通知时,Listener 便能发挥它的作用。以及JobStores,持久化,将任务调度相关数据保存下来。

4)、总之,Quartz设计核心类包括Schedule,Job以及Trigger。其中Job负责定义需要执行的任务,Trigger负责设置调度策略,Schedule将二者组装在一起,并触发任务开始执行。

(二)、JCrontab

1、JCrontab和Quartz相比:

1)、支持多种任务调度持久化方法,包括普通文件、数据库以及XML文件进行持久化;

2)、JCrontab与web服务器结合非常方便,任务调度可以随着Web应用服务器的启动而自动启动;

3)、Jcrontab还内置了发邮件功能,可以将任务执行结果方便地发送给需要通知的人。

2、编程步骤一:在web.xml中配置JCrontab的属性

	<!--用Jcrontan设置调用任务-->
	<servlet>
		<servlet-name>LoadOnStartupServlet</servlet-name>
		<!-- 整个任务调度的入口 -->
		 <servlet-class>org.jcrontab.web.loadCrontabServlet</servlet-class>
		 <!-- PROPERTIES_FILE此参数才能被loadCrontabServlet所识别  -->
		<init-param>
			<param-name>PROPERTIES_FILE</param-name>
			<!--此处路径是绝对路径 -->
			<param-value>/opt/tomcat7/webapps/trade/jcrontab.properties</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>  
  	  <servlet-name>LoadOnStartupServlet</servlet-name>  
  	  <url-pattern>/Startup</url-pattern>  
	</servlet-mapping

1)、必须指定servlet-class为org.jcrontab.web.loadCrontabServlet,整个任务调度入口;

2)、必须指定一个参数为PROPERTITES_FILE,才能被loadCrontabServlet识别,并且为绝对路径

/opt/tomcat7/webapps/trade/jcrontab.properties

3、编程步骤二:编写jcrontab.properties文件

org.jcrontab.data.file=/opt/tomcat7/webapps/trade/crontab
org.jcrontab.data.datasource=org.jcrontab.data.FileSource
大体意思是:dataSource数据来源被描述为普通文件org.jcrontab.data.FileSource,具体指的是:
/opt/tomcat7/webapps/trade/crontab
4、编程步骤三:描述任务调度安排文件crontab(类名#run

# Tasks planification configuration file.
# IMPORTANT: All the index begin in 0, except day of month and
#       month that begin in 1.
#              minute         0-59
#              hour           0-23
#              day of month   1-31
#              month          1-12
#              day of week    0-6 (0 is Sunday)
#测试代码------每三分钟执行一次 CrontabMainTest的 main方法
*/3 * * * * traderapidemo.mysql.TimeTest#run
#测试代码------连接MySQL
*/2 * * * * traderapidemo.mysql.SelectDataFromMysql#run
TimeTest.java实际和之前的Timer有异曲同工之处,将具体时间封装在一个线程中:

public class TimeTest implements Runnable {
	public void run() {
		System.out.println("--------------------------------------------------------------------------------");
		System.out.println("每隔三分钟打印一次日期");
		System.out.println(new Date());
		// 打印日期方便在log日志中检测
		SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		String format = simpleDateFormat.format(new Date());
		System.out.println("-----------------------------" + format + "------------------------------");
	}
}

周一到周五,没两分钟打印从mysql中某表的数据:

public class SelectDataFromMysql implements Runnable {
	// 连接Mysql的4个URL
	private static String driver = "com.mysql.jdbc.Driver";
	private static String url = "jdbc:mysql://192.168.0.121/instrument?characterEncoding=UTF8&zeroDateTimeBehavior=convertToNull";
	private static String user = "root";
	private static String password = "";
	public static java.sql.Connection connection;

	// 链接Mysql
	public static java.sql.Connection getConnection() {
		try {
			// 初始化驱动
			Class.forName(driver);
			// 根据参数获得Connection对象
			connection = DriverManager.getConnection(url, user, password);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return connection;
	}

	// 测试是否连接成功
	public static void isOrNotConnect() {
		Connection c = getConnection();
		if (c == null) {
			System.out.println("与MySQL数据库连接失败!");
		} else {
			System.out.println("与MySQL数据库连接成功!");
		}
	}

	public void run() {
		System.out.println("--------------------------------------------------------------------------------");
		System.out.println("测试连接MySQL是否成功。");
		isOrNotConnect();
		System.out.println(new Date());
		Statement createStatement = null;
		try {
			createStatement = getConnection().createStatement();
			ResultSet rs = createStatement.executeQuery("select * from config");
			while (rs.next()) {
				String id = rs.getString("product_id");
				String plate = rs.getString("part");
				System.out.println(id + "**********" + plate);
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
}
5、运行结果

Java定时任务调度

四、总结

1、通过上面几种任务调度比较,各有优缺点。简单单任务程用Timer;简单多任务用简单多任务用Executors.newScheduledThreadPool(10)线程池;又比如说周一到周五每天下午15:13:14这种比较复杂的定时调度任务用Quartz和JCrontab,当然有web项目则JCrontab没得说;

2、关于crontab文件的编写语法(比如05 16 * * 1-5)网上很多,一看便会,如有问题欢迎指教和讨论。