任务调度之Java Timer

1、前言
 
在Java中如果需要定时执行某些任务,可以使用java.util包提供Timer和TimerTask,在后台线程中调度任务。 简单来说,TimerTask是要执行的任务,Timer是调度程序。
 
2、运行一个定时任务
 
使用Timer的来运行一个定时任务
@Test
public void testTimer_SchedulingTaskOnce() throws Exception {
    TimerTask task = new TimerTask() {
        public void run() {
            System.out.println("Task performed on: " + new Date() + "n" + "Thread's name: " + Thread.currentThread().getName());
        }
    };
    Timer timer = new Timer("Timer");
    long delay = 1000L;
    timer.schedule(task, delay);
    Thread.sleep(delay * 2);
}

3. 重复执行定时任务

 如何定义一个以预定义的时间间隔运行的任务。使用scheduleAtFixedRate(repeatedTask,delay,period)方法。它在指定的延迟(delay)之后,间隔指定的时间段(period),重复执行的任务。
@Test
public void testTimer_SchedulingRepeatedTask() throws InterruptedException {
    TimerTask repeatedTask = new TimerTask() {
        public void run() {
            System.out.println("Task performed on " + new Date());
        }
    };
    Timer timer = new Timer("Timer");
    long delay  = 1000L;
    long period = 1000L;
    timer.scheduleAtFixedRate(repeatedTask, delay, period);
    Thread.sleep(delay * 10);
}

如果执行因为一些原因(例如垃圾收集或其他后台活动)而延迟,则会快速连续执行两次或更多次执行以“赶上”正常的执行次数。

 
3.1 定义每天执行的任务
下面,定义一个每天都执行的任务:
@Test
public void testTimer_SchedulingDailyTask() {
    TimerTask repeatedTask = new TimerTask() {
        public void run() {
            System.out.println("Task performed on " + new Date());
        }
    };
    Timer timer = new Timer("Timer");
    long delay = 1000L;
    long period = 1000L * 60L * 60L * 24L;
    timer.scheduleAtFixedRate(repeatedTask, delay, period);
}

 

4.取消定时器和定时任务

 
可以通过以下几种方式取消任务的执行:
 
4.1 取消运行中的TimerTask
TimerTask本身的实现类里,通过在run()方法的中调用TimerTask.cancel()方法:
@Test
public void testTimer_CancelingTimerTask() throws InterruptedException {
    TimerTask task = new TimerTask() {
        public void run() {
            System.out.println("Task performed on " + new Date());
            cancel();
        }
    };
    Timer timer = new Timer("Timer");
    timer.scheduleAtFixedRate(task, 1000L, 1000L);
    Thread.sleep(1000L * 10);
}

执行上面的代码,会发现TimerTask只被执行了一次。

 
4.2 取消定时器
通过在Timer对象上调用Timer.cancel()方法:
@Test
public void testTimer_CancelingTimer() throws InterruptedException {
    TimerTask task = new TimerTask() {
        public void run() {
            System.out.println("Task performed on " + new Date());
        }
    };
    Timer timer = new Timer("Timer");
    timer.scheduleAtFixedRate(task, 1000L, 1000L);
    Thread.sleep(1000L * 1);
    timer.cancel();
    Thread.sleep(1000L * 9);
}

执行上面的代码,会发现TimerTask也只被执行了一次。

 
4.3 在运行中停止TimerTask的Thread
还可以在任务的run方法中停止线程,从而取消整个任务: 注意运行实现中的TODO指令 - 为了运行这个简单的例子,我们需要实际停止线程。Thread类的stop方法,这个方法已经被废弃了。
@Test
public void testTimer_StoppingThread()throws InterruptedException {
    TimerTask task = new TimerTask() {
        public void run() {
            System.out.println("Task performed on " + new Date());
            // TODO: 假如在这里停止
            Thread.currentThread().stop();
        }
    };
    Timer timer = new Timer("Timer");
    timer.scheduleAtFixedRate(task, 1000L, 1000L);
    Thread.sleep(1000L * 2);
}

在实际的自定义线程实现中,一般会支持停止线程。

 
5.Timer VS ExecutorService
可以利用ExecutorService来执行定时器任务,而不是使用定时器。以下是如何以指定间隔运行重复任务的示例:
@Test
public void testTimer_SchedulingRepeatedTask_ByScheduledExecutorService() throws InterruptedException {
    TimerTask repeatedTask = new TimerTask() {
        public void run() {
            System.out.println("Task performed on " + new Date());
        }
    };
    ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
    long delay = 1000L;
    long period = 1000L;
    executor.scheduleAtFixedRate(repeatedTask, delay, period, TimeUnit.MILLISECONDS);
    Thread.sleep(delay + period * 3);
    executor.shutdown();
}

那么Timer和ExecutorService之间的主要区别是什么:

  1. 定时器可以对系统时钟的变化敏感; ScheduledThreadPoolExecutor不是
  2. Timer只有一个执行线程; ScheduledThreadPoolExecutor可以配置任意数量的线程
  3. 在TimerTask中抛出的运行时异常会杀死一个线程,从而导致Timer死机:(即计划任务将不再运行。ScheduledThreadExecutor不仅捕获运行时异常,还允许在需要时处理它们(通过重写afterExecute方法ThreadPoolExecutor)。抛出异常的任务将被取消,但其他任务将继续运行。
在 JDK1.5 之后,基本不使用 Timer 进行任务调度了。
 
6 总结
 
使用Java内置的简单而灵活的Timer和TimerTask基础结构的许多方法,可以快速实现调度任务。如果需要更复杂和完整的解决方案,在Java的世界里还有Quartz等开源框架。
 
posted @ 2023-03-22 11:41  jrliu  阅读(46)  评论(0编辑  收藏  举报