在做后台任务的时候经常需要实现各种各种的定时的,周期性的任务。比如每隔一段时间更新一下缓存之类的。通常周期性的任务都可以使用如下方式实现:
class MyTimerThread extends Thread {
@Override
public void run() {
while(true) {
try {
Thread.sleep(60*1000);
//每隔1分钟需要执行的任务
doTask();
} catch (Exception e) {
e.printStackTrace();
}
}
};
}
其实用这种方式我还没遇到过什么问题。网上有人说调用线程sleep()方法会导致线程休眠时还是会占用cpu资源不释放(而wait()不会),这种说法应该是不正确的。若有人知道其中存在的问题,敬请告知!由于这种实现一般都是一个线程对于一个定时任务,且没有实现在指定时间启动任务(也可以实现,加个时间判断就可以了)。
/**
* 这个方法是调度一个task,经过delay(ms)后开始进行调度,仅仅调度一次
*/
public void schedule(TimerTask task, long delay)
/**
* 在指定的时间点time上调度一次
*/
public void schedule(TimerTask task, Date time)
在指定的时间点time上调度一次。
/**
* 周期性调度任务,在delay(ms)后开始调度。
* 并且任务开始时间的间隔为period(ms),即“固定间隔”执行
*/
public void schedule(TimerTask task, long delay, long period)
/**
* 和上一个方法类似,唯一的区别就是传入的第二个参数为第一次调度的时间
*/
public void schedule(TimerTask task, Date firstTime, long period)
public void scheduleAtFixedRate(TimerTask task, long delay, long period)
public void scheduleAtFixedRate(TimerTask task, Date firstTime,long period)
public static void main(String[] args) {
TimerTask task = new TimerTask() {
@Override
public void run() {
System.out.println("do task.......");
}
};
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Timer timer = new Timer();
try {
timer.schedule(task, sdf.parse("2016-4-9 00:00:00"), 5000);
//timer.scheduleAtFixedRate(task, sdf.parse("2016-4-9 00:00:00"),5000);
} catch (ParseException e) {
e.printStackTrace();
}
}
public class Timer {
// 任务队列
private final TaskQueue queue = new TaskQueue();
// 处理线程
private final TimerThread thread = new TimerThread(queue);
通过这种单线程的方式实现,在存在多个定时任务的时候便会存在问题:
若任务B执行时间过长,将导致任务A延迟了启动时间!
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
System.out.println(sdf.format(new Date()) + " A: do task");
}
}, 0, 5*1000);
timer.schedule(new TimerTask() {
@Override
public void run() {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
System.out.println(sdf.format(new Date()) + " B: sleep");
try {
Thread.sleep(20*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, 10*1000, 5000);
timer.schedule(new TimerTask() {
@Override
public void run() {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
System.out.println(sdf.format(new Date()) + " C: throw Exception");
throw new RuntimeException("test");
}
}, 30*1000, 5000);
}
通过以上程序发现:一开始,任务A能正常每隔5秒运行一次。在任务B启动后,由于任务B运行时间需要20秒,导致任务A要等到任务B执行完才能执行。更可怕的是,任务C启动后,抛了个异常,定时任务挂了!
/**
* 调度一个task,经过delay(时间单位由参数unit决定)后开始进行调度,仅仅调度一次
*/
public ScheduledFuture> schedule(Runnable command,
long delay, TimeUnit unit);
/**
* 同上,支持参数不一样
*/
public ScheduledFuture schedule(Callable callable,
long delay, TimeUnit unit);
/**
* 周期性调度任务,在delay后开始调度,适合执行时间比“间隔”短的任务
* 并且任务开始时间的间隔为period,即“固定间隔”执行。
* 如果任务执行的时间比period长的话,会导致该任务延迟执行,不会同时执行!
* 如果任务执行过程抛出异常,后续不会再执行该任务!
*/
public ScheduledFuture> scheduleAtFixedRate(Runnable command,
long initialDelay ,long period ,TimeUnit unit);
/**
* Timer所没有的“特色”方法,称为“固定延迟(delay)”调度,适合执行时间比“间隔”长的任务
* 在initialDelay后开始调度该任务
* 随后,在每一次执行终止和下一次执行开始之间都存在给定的延迟period
* 即下一次任务开始的时间为:上一次任务结束时间(而不是开始时间) + delay时间
* 如果任务执行过程抛出异常,后续不会再执行该任务!
*/
public ScheduledFuture> scheduleWithFixedDelay(Runnable command,
long initialDelay ,long delay ,TimeUnit unit);
public static void main(String[] args) {
int corePoolSize = 3;
ScheduledExecutorService pool = Executors.newScheduledThreadPool(corePoolSize);
pool.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
System.out.println(sdf.format(new Date()) + " A: do task");
}
}, 0 ,5, TimeUnit.SECONDS);
pool.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
System.out.println(sdf.format(new Date()) + " B: sleep");
try {
Thread.sleep(20*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, 10, 5, TimeUnit.SECONDS);
pool.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
System.out.println(sdf.format(new Date()) + " C: throw Exception");
throw new RuntimeException("test");
}
}, 30, 5, TimeUnit.SECONDS);
}
由于有3个任务需要调度,因此我将corePoolSize设置为3。通过控制台打印可以看到这次任务A一直都在正常运行(任务时间间隔为5秒),并不受任务B的影响。任务C抛出异常后,虽然本身停止了调度,但没有影响到其他任务的调度。可以说ScheduledThreadPoolExecutor解决Timer存在的问题!
ScheduledExecutorService pool = Executors.newSingleThreadScheduledExecutor();