Java实现定时任务有哪些方式?
sleep
这也是我们最常用的sleep 休眠大法,不只是当作休眠用,我们还可以利用它很轻松的能实现一个简单的定时任务。
实现逻辑:
新开一个线程,添加一个 for/ while 死循环,然后在死循环里面添加一个sleep 休眠逻辑,让程序每隔N秒休眠再执行一次,这样就达到了一个简单定时任务的效果。
实现代码如下:
private static void sleepTask() {
new Thread(() -> {
while (true) {
System.out.println("你好");
try {
//3秒执行一次
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
).start();
}
这种方式比较傻瓜化了,只能按固定频率运行,不能指定具体运行的时间。
Timer
JDK 1.3就内置了java.util.Timer类,可以用来调度java.util.TimerTask任务。
几个重要的方法:
- schedule:开始调度任务,提供了几个包装方法
- cancle:终止任务调度,取消当前调度的所有任务,正在运行的任务不受影响
- purge:从任务队列中移除所有已取消的任务
另外, java.util. TimerTask就是实现了Runnable接口,具体任务逻辑则是在run方法里去实现。
具体方法实现如下
public static void main(String[] args) {
Timer timer = new Timer();
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
System.out.println("你好");
}
};
//第一次任务延迟时间
long delay = 3000L;
//任务执行频率
long period = 3 * 100;
//开始调度
timer.schedule(timerTask, delay, period);
//指定首次运行时间
//timer.schedule(timerTask, DateUtil.offsetDay(new Date(), 1), period);
//终止并移除任务
timer.cancel();
timer.purge();
}
这种实现方式比较简单,可以指定首次执行的延迟时间、首次执行的具体日期时间,以及执行频率,能满足日常需要。
另外,需要注意的是,Timer是线程安全的,因为背后是单线程在执行所有任务。
Timer 也会有一些缺陷:
-
Timer是单线程的,假如有任务A,B,C,任务A如果执行时间比较长,那么就会影响任务B,C的启动和执行时间,如果B,C执行时间也比较长,那就会相互影响;
-
Timer不会捕获异常,如果A,B,C任何一个任务在执行过程中发生异常,就会导致TImer整个定时任务停止工作;
-
Timer是基于绝对时间调度的,而不是基于相对时间,所以它对系统时间的改变非常敏感;
所以,如果在使用Timer的过程中要注意这些缺陷,虽然可以用,但不推荐。
ScheduledExecutorService
因Timer有一些缺陷,所以不太建议使用Timer,推荐使用ScheduledExecutorService:
ScheduledExecutorService 即是Timer的替代者,JDK1.5并发包引入,是基于线程池设计的定时任务类:
java.util.concurrent.Executors.newScheduledThreadPool
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}
public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1, threadFactory));
}
上了线程池,每个调度任务都会分配到线程池中的某一个线程去执行,任务就是并发调度执行的,任务之间互不影响。
几个重要的调度方法:
-
schedule:只执行一次调度
-
scheduleAtFixedRate:按固定频率调度,如果执行时间过长,下一次调度会延迟,不会同时执行
-
scheduleWithFixedDelay:延迟调度,上一次执行完再加上延迟时间后执行
另外,可以看出,任务是支持Runnable和Callable 调度的。
public static void main(String[] args) {
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(10);
executorService.scheduleAtFixedRate(() ->{
System.out.println("你好");
},2000,3000, TimeUnit.MILLISECONDS);
}
这是一个按固定频率调度的任务,创建了10个核心线程数,首次执行延迟2秒,后续每3秒执行一次。
这种方式简单、好用,避免了使用Timer 带来的各种问题,推荐使用这种实现方式。