欢迎光临
感受代码之美

Java中Timer和ThreadPoolExecutor的区别和比较

今天闲来无事读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学习比较系列文章:

  1. Java中Timer和ThreadPoolExecutor的区别和比较
  2. Java中使用Timer和TimerTask完成定时执行任务
  3. Java中ScheduledThreadPoolExecutor实现定时周期性任务
  4. 详细了解Java中定时器Timer的使用及缺陷分析
转载请注明来源:四个空格 » Java中Timer和ThreadPoolExecutor的区别和比较

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址