今天闲来无事读Java中Timer的源码,发现Timer的注释中有这么一段
Java 5.0 introduced the {@code java.util.concurrent} package and
one of the concurrency utilities therein is the {@link
java.util.concurrent.ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor} which is a thread pool for repeatedly
executing tasks at a given rate or delay. It is effectively a more
versatile replacement for the {@code Timer}/{@code TimerTask}
combination, as it allows multiple service threads, accepts various
time units, and doesn’t require subclassing {@code TimerTask} (just
implement {@code Runnable}). Configuring {@code
ScheduledThreadPoolExecutor} with one thread makes it equivalent to
{@code Timer}.
其大意是说Timer的API有单线程的问题,一个定时器不允许同时执行任务。官方说在JDK1.5之后引入了ScheduledThreadPoolExecutor
类来实现多线程的定时器。那就来看看到底是怎么回事把。
Timer
package com.freud.test;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class TimerTest {
private static long start = System.currentTimeMillis();
public static void main(String[] args) {
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
try {
System.out.println("Timer 1 start at:"
+ (System.currentTimeMillis() - start));
Thread.sleep(1000);
System.out.println("Timer 1 finish at:"
+ (System.currentTimeMillis() - start));
// throw new RuntimeException("Exception occured here!");
} catch (InterruptedException e) {
System.out.println("Thread was interrupted.");
}
}
}, new Date(), 1000);
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
try {
System.out.println("Timer 2 start at:"
+ (System.currentTimeMillis() - start));
Thread.sleep(1000);
System.out.println("Timer 2 finish at:"
+ (System.currentTimeMillis() - start));
} catch (InterruptedException e) {
System.out.println("Thread was interrupted.");
}
}
}, new Date(), 1000);
}
}
执行上述代码后会发现打印输出如下:
Timer 1 start at:1
Timer 1 finish at:1001
Timer 2 start at:1001
Timer 2 finish at:2001
Timer 2 start at:2001
Timer 2 finish at:3001
Timer 1 start at:3001
Timer 1 finish at:4001
Timer 1 start at:4001
Timer 1 finish at:5001
Timer 2 start at:5001
Timer 2 finish at:6002
Timer 2 start at:6002
Timer 2 finish at:7002
Timer 1 start at:7002
Timer 1 finish at:8002
Timer 1 start at:8002
Timer 1 finish at:9002
Timer 2 start at:9002
Timer 2 finish at:10002
Timer 2 start at:10002
Timer 2 finish at:11002
Timer 1 start at:11002
通过日志不难看出,在一个Timer中的2个TimerTask是有执行顺序的,也就是上一个没有执行完,下一个是不会触发的。而究其原因,看源码发现Timer的实现中只有一个TimerThread线程通过while (true)的loop来执行存储在TaskQueue中的定时器任务
public class Timer {
/**
* The timer task queue. This data structure is shared with the timer
* thread. The timer produces tasks, via its various schedule calls,
* and the timer thread consumes, executing timer tasks as appropriate,
* and removing them from the queue when they're obsolete.
*/
private final TaskQueue queue = new TaskQueue();
/**
* The timer thread.
*/
private final TimerThread thread = new TimerThread(queue);
并且在loop的过程中只捕获了InterruptedException导致其中一个Task出现错误,会影响接下来的所有Task的执行。
/**
* The main timer loop. (See class comment.)
*/
private void mainLoop() {
while (true) {
try {
//*****
if (taskFired) // Task fired; run it, holding no locks
task.run();
} catch(InterruptedException e) {
}
}
}
打开TimerTest中注释掉的throw new RuntimeException("Exception occured here!");
代码,会发现Task2不会执行!
ScheduledThreadPoolExecutor
package com.freud.test;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledThreadPoolExecutorTest {
private static long start = System.currentTimeMillis();
public static void main(String[] args) {
ScheduledExecutorService schedule = Executors.newScheduledThreadPool(1);
schedule.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
System.out.println("Schedule 1 start at:"
+ (System.currentTimeMillis() - start));
Thread.sleep(1000);
System.out.println("Schedule 1 finish at:"
+ (System.currentTimeMillis() - start));
// throw new RuntimeException("Exception occured here!");
} catch (InterruptedException e) {
System.out.println("Thread was interrupted.");
}
}
}, 1, 1, TimeUnit.SECONDS);
schedule.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
System.out.println("Schedule 2 start at:"
+ (System.currentTimeMillis() - start));
Thread.sleep(1000);
System.out.println("Schedule 2 finish at:"
+ (System.currentTimeMillis() - start));
} catch (InterruptedException e) {
System.out.println("Thread was interrupted.");
}
}
}, 1, 1, TimeUnit.SECONDS);
}
}
观察到的结果如下,可以发现是跟Timer的执行结果是一样的.
Schedule 1 start at:1003
Schedule 1 finish at:2004
Schedule 2 start at:2004
Schedule 2 finish at:3004
Schedule 1 start at:3004
Schedule 1 finish at:4004
Schedule 2 start at:4004
Schedule 2 finish at:5004
Schedule 1 start at:5004
Schedule 1 finish at:6004
Schedule 2 start at:6004
Schedule 2 finish at:7004
Schedule 1 start at:7004
此时,修改Executors.newScheduledThreadPool(1)为Executors.newScheduledThreadPool(2)创建两个线程来执行定时器任务,观察到结果预期应该是每秒钟2个任务同时执行,结果如下,符合预期。
Schedule 1 start at:1004
Schedule 2 start at:1004
Schedule 2 finish at:2005
Schedule 1 finish at:2005
Schedule 2 start at:2005
Schedule 1 start at:2005
Schedule 1 finish at:3005
Schedule 1 start at:3005
Schedule 2 finish at:3009
Schedule 2 start at:3009
Schedule 1 finish at:4005
Schedule 1 start at:4005
Schedule 2 finish at:4009
Schedule 2 start at:4009
当打开throw new RuntimeException("Exception occured here!");
之后会发现Schedule2正常执行,Schedule1的异常不会影响Schedule2的执行!
Schedule 1 start at:1003
Schedule 2 start at:1004
Schedule 1 finish at:2004
Schedule 2 finish at:2004
Schedule 2 start at:2004
Schedule 2 finish at:3004
Schedule 2 start at:3004
Schedule 2 finish at:4004
Schedule 2 start at:4004
Schedule 2 finish at:5004
Schedule 2 start at:5004
Schedule 2 finish at:6004
Schedule 2 start at:6004
结论
Timer可以做的事情,通过ScheduledThreadPoolExecutor
都可以做到,并且修复了其中Timer的部分设计上的缺陷。并且实现了多线程的执行,相对来说功能是比Timer强大了一个等级。
本文转载自:http://www.hifreud.com/2016/06/02/java-05-Timer-and-ScheduledThreadPoolExecutor/ ;
Java中Timer和ThreadPoolExecutor学习比较系列文章: