定时器
之前做项目的时候接触到微信公众平台开发,在之前的文章里面有提到过需要设置定时任务,所以本文就讲一下java的几种定时任务如何实现。
首先说一下本文介绍的定时器,一共三种,分别是通过Thread
,Timer
,spring-quartz
实现,没有涉及spring-task
以及其他的实现方法,如果你对于这三种实现不感兴趣,那就关了网页,别浪费时间了。
接下来是正文介绍
Thread实现定时任务
首先我们看一下,Thread
线程想必大家都比较了解,而使用线程实现定时就是利用了线程的sleep()
方法,如下代码的简单实现
package util;
/**
* Created by yubotao on 2017/11/21.
*/
/**
* 通过线程实现定时
*
* */
public class ThreadUtil implements Runnable{
public void run(){
while(true){
try{
for(int i = 0; i < 100; i++) {
System.out.println("==========> come on");
System.out.println("it's in for : " + i);
System.out.println("Util Thread id :" + Thread.currentThread().getId());
Thread.sleep(10*1000);
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
/**
* 就测试结果来看,确实是多线程而不是单线程
* */
public static void main(String[] args) {
new Thread(new ThreadUtil()).start();
for(int i = 0; i < 1000; i++){
System.out.println("真的不行吗?");
try {
System.out.println("main Thread id :" + Thread.currentThread().getId());
Thread.sleep(15000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
这里是使用自定义线程的方法,自定义线程需要实现Runnable
接口中的run()
方法,可以看到我们在方法中用了一个死循环,循环中有这一行关键代码
Thread.sleep(10*1000);
表示该线程休眠10s,然后进入就绪状态,等待执行。
可以看一下运行结果
然后说一下这种定时方式的弊端:
首先,我们知道多线程一直会是一个比较麻烦的事,很容易出现很多问题,所以这里使用的时候就要多加小心;
其次,我们使用的sleep()
方法只是将线程至于休眠状态,然后之后恢复就绪状态,但是并不是立即运行的,如果当前cpu的线程队列比较多,会导致该线程无法立即启动,这时定时任务就会出现偏差,并且不断的影响后续部分的定时任务,导致整个定时器废掉。
不过这个问题是可以通过设置线程优先级解决的,但是这里我暂时还没有了解,所以这个地方可以自己查看一下。
另外,定时器的启动一般都是在项目启动的时候只启动一次,所以,该任务放到那部分代码里就需要好好考量一番了,之前我就犯了放到Controller接口中的错误,这样每次请求接口都会新开一个定时线程,会出现很严重的问题。
Timer
Timer是java自带的定时类,位于java.util包中。
使用自定义的Timer类需要继承TimerTask类,同样位于java.util包中。
而这个TimerTask查看源码就会发现,它其实也是通过线程实现的定时。
public abstract class TimerTask implements Runnable
该类同样实现Runnable
接口,那么就是和线程类似,但是要远比自己手动的线程实现严谨的多,里面已经设计好了锁等方式保证定时任务不会出现之前提到的严重问题。
那么看一下我们如何自定义Timer
package util;
import java.util.Timer;
import java.util.TimerTask;
/**
* Created by yubotao on 2017/11/21.
*/
/**
* java的定时任务类Timer
* */
public class TimerUtil extends TimerTask{
private String abc;
@Override
public void run(){
for(int i = 0; i < 2; i++){
abc = "" + i;
System.out.println("abc = " + abc);
}
System.out.println("end");
}
public static void main(String[] args) {
Timer timer = new Timer();
//Timer定时器,第一个参数delay是延迟时间,第二个参数period是时间间隔
timer.schedule(new TimerUtil(),1000,2000);
}
}
我们看到Timer启动是有特定方法以及参数的,运行结果如下
这个和之前讲到的Thread差不多,问题也是具有相似性的,不过这里做了一个改善就是,如果定时器在设定的时间内没有立即运行,那么这个定时器将会停止不再运行,这个比我们当时手动编写的Thread要好很多,虽然定时器不再运行,但是保证不会因为时间误差导致的严重问题;当然有时候立即停止可能也不是好事,这个需要看具体场景去衡量。
spring-quartz
quartz —— 石英
我们都知道石英表,这个组件的名字就是取此意,拿来表示定时器再好不过。而Spring和quartz的整合可以让我们通过简单的配置,在Spring的项目启动的时候就运行该定时器,十分方便。
下面就让我们看一下如何通过配置实现。
我们先定义定时器内的方法,我们需要定时器做什么内容
package util;
import java.text.SimpleDateFormat;
/**
* Created by yubotao on 2017/11/21.
*/
/**
* spring-quartz的定时器的具体实现方法
* */
public class quartzUtil {
public void test(){
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("start? " + simpleDateFormat.format(System.currentTimeMillis()));
}
}
接下来就是配置文件了
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
">
<bean id="business" class="util.quartzUtil"/>
<!--初始化调度任务-->
<bean name="simpleTrigger" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<!--调度的类-->
<property name="targetObject" ref="business"/>
<!--调度的方法-->
<property name="targetMethod" value="test"/>
</bean>
<!--触发器-->
<bean id="cronTriggerTest" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<!--指向的任务-->
<property name="jobDetail" ref="simpleTrigger"/>
<!--时间定时-->
<!--参数依次为秒,分,时,日,月;还有其他参数,具体查阅资料;注意参数哪里,*是一个很关键的因素,不使用的地方要用*,设置0相当于有参数-->
<property name="cronExpression" value="0/5 * * * * ?"/>
</bean>
<!--总调度器-->
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<!--触发器列表-->
<ref bean="cronTriggerTest"/>
</list>
</property>
</bean>
</beans>
这里简单的对该配置文件进行一下讲解。
首先是
<bean id="business" class="util.quartzUtil"/>
将我们的定时器的实现方法注册为bean。
接下来
<!--初始化调度任务-->
<bean name="simpleTrigger" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<!--调度的类-->
<property name="targetObject" ref="business"/>
<!--调度的方法-->
<property name="targetMethod" value="test"/>
</bean>
我们为quartz设置调度任务,这里面需要配置的就是定义的定时器类及方法。
然后
<!--触发器-->
<bean id="cronTriggerTest" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<!--指向的任务-->
<property name="jobDetail" ref="simpleTrigger"/>
<!--时间定时-->
<!--参数依次为秒,分,时,日,月;还有其他参数,具体查阅资料;注意参数哪里,*是一个很关键的因素,不使用的地方要用*,设置0相当于有参数-->
<property name="cronExpression" value="0/5 * * * * ?"/>
</bean>
设置quartz的触发器,触发器中配置了之前设置的调度任务以及定时格式,比如我这里设定的就是该任务每5s执行一次。
最后
<!--总调度器-->
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<!--触发器列表-->
<ref bean="cronTriggerTest"/>
</list>
</property>
</bean>
是一个总的调度器,这里有相应的触发器列表,可以设计列表执行顺序,也就是说我们可以设定多个定时器,并按照顺序执行,这也是它的优势所在。
之后我们看一下项目启动后,该定时器的运行结果
到此为止三种定时器的实现介绍结束了。