在工作中不免会碰到定时任务,我将我这次编写定时任务做一个记录,
java中JDk提供了两种定时任务实现
一.Timer
Timer是最简单的一种定时任务
实现代码如下
import java.util.TimerTask;
/**
* <p>
* 创建一个类继承TimerTask重写run方法
* </p>
*
* @author Alemand
* @since 2018/2/26
*/
public class TimeTest extends TimerTask {
private int i;
public TimeTest(int i) {
this.i = i;
}
/**
* 重写自Timertask,run方法执行的就是具体的定时任务
*/
@Override
public void run() {
System.out.println(i);
}
}
使用Timer的核心就是Timer和TimerTask.TimerTask继承线程记录任务的状态
Timer负责时间的调度,Timer的核心实现是一个TimerThread和TaskQueue,Timer调用方法的时候就是将TimerTask的放到queue中,TimerThread是一个守护线程用来执行queue的TimerTask.这个线程会轮询所有任务,找到一个最近要执行的任务,然后休眠,当到达最近要执行任务的开始时间点,TimerThread 被唤醒并执行该任务。之后 TimerThread 更新最近一个要执行的任务,继续休眠。
Timer 的优点在于简单易用,但由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务。
二.线程池 ScheduledExecutor
线程池ScheduledExecutor弥补了上面Timer的缺陷,它的设计思想每一个任务就是线程池的一个任务,因此任务是并行的,相互之间不受干扰,注意的是,只有当任务的执行时间到来时,ScheduedExecutor 才会真正启动一个线程,其余时间 ScheduledExecutor 都是在轮询任务的状态。
具体的代码如下
public class Test1 {
public static void main(String[] args) {
//使用Executors创建线程池
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println(1);
}
}, 10, TimeUnit.SECONDS);
}
}
ScheduledExecutorService方法介绍
schedule(Runnable command,long delay, TimeUnit unit);schedule(Callable command,long delay, TimeUnit unit);
上面两个方法是方法的重载一个参数都是要穿一个线程,可以是Runable,也可以是Callable,当穿Callable会将线程的
结果返回去,第二个是延迟时间,第三个参数是计时单位
scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit);
scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit);
这两个方法是周期执行方法,第一个参数是一个线程,第二个是延迟时间,第三个是周期时间,第四个计时单位
scheduleAtFixedRate与scheduleWithFixedDelay的区别是
scheduleAtFixedRate是以延迟后的执行时间为基准的举例你在12:00创建了一个延迟10分执行的任务周期时间为一个小时
那么它会在13:10.14:10 ...是以12:10分为基准加上n*周期时间执行的
scheduleWithFixedDelay是以上次任务的开始时间为基准的,举例12:00创建了一个延迟10分执行的任务周期时间为20分
那么它会在12.30.12:50 ...是以上次开始分为基准加上周期时间执行的
采用这两种方式编写定时任务是时都会有问题具体的什么问题
1.定时任务太多,线程池满了怎么办
2.当服务发生突发情况那么前的定时任务都会丢失