浅谈java定时器的发展历程

时间:2022-09-18 11:52:09

在开发中,我们经常需要一些周期性的操作,例如每隔几分钟就进行某一项操作。这时候我们就要去设置个定时器,Java中最方便、最高效的实现方式是用java.util.Timer工具类,再通过调度java.util.TimerTask任务。

Timer是一种工具,线程用其安排以后在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行。实际上是个线程,定时调度所拥有的TimerTasks。

TimerTask是一个抽象类,它的子类由Timer安排为一次执行或重复执行的任务。实际上就是一个拥有run方法的类,需要定时执行的代码放到run方法体内。

java在jdk1.3中推出了定时器类Timer,而后在jdk1.5后由DouLea从新开发出了支持多线程的ScheduleThreadPoolExecutor,从后者的表现来看,可以考虑完全替代Timer了。

Timer与ScheduleThreadPoolExecutor对比:

1.Timer始于jdk1.3,其原理是利用一个TimerTask数组当作队列,将所有定时任务添加到此队列里面去。然后启动一个线程,当队列为空时,此线程会阻塞,当队列里面有数据时,线程会去除一个TimerTask来判断

是否到时间需要运行此任务,如果运行时间小于或等于当前时间时则开始运行任务。由于其单线程的本质,所以会带来几个问题(详细代码在后面):

第一,当我们添加到定时器中的任务比较耗时时,由于此定时器是单线程顺序执行定时器任务,所以会影响后续任务的按时执行。

Java代码

?
1
2
3
4
5
6
7
8
9
10
11
12
13
//问题一示例:
m_timer.scheduleAtFixedRate(new TaskUseLongTime(), 1000, 5000);
m_timer.scheduleAtFixedRate(new TaskNormal(), 5000, 3000);
 
运行结果:
14:44:29: timer is sleeping 10 seconds
14:44:39: Task Normal executed
14:44:39: timer is sleeping 10 seconds
14:44:49: Task Normal executed
14:44:49: Task Normal executed
14:44:49: timer is sleeping 10 seconds
 
结果分析:TaskNormal任务无法保证3秒运行一次,其只能等待TaskUseLongTime运行结束后才可以。

第二,Timer中的线程仅仅会捕获InterruptedException异常,所以如果我们自定义的定时任务里面没有捕获可能出现的异常而导致异常抛出后,

?
1
2
3
4
5
6
7
8
9
10
11
12
13
//问题二示例:
m_timer.schedule(new TaskThrowException(), 1000);
m_timer.schedule(new TaskNormal(), 2000);
 
运行结果:
14:47:37: Throw exception
Exception in thread "Timer-0" java.lang.RuntimeException
  at timer_test.TimerTest$TaskThrowException.run(TimerTest.java:85)
  at java.util.TimerThread.mainLoop(Timer.java:512)
  at java.util.TimerThread.run(Timer.java:462)
 
结果分析:
当前一个任务抛出异常后,后面的TaskNormal任务无法继续运行

会导致我们的Timer线程停止,从而另后续的任务无法执行。

第三,其无法处理多个同时发生的定时任务

?
1
2
3
4
5
6
7
8
9
10
11
//问题三示例:
m_timer.scheduleAtFixedRate(new TaskUseLongTime("timer1"), 1000, 15000);
m_timer.scheduleAtFixedRate(new TaskUseLongTime("timer2"), 1000, 15000);
 
运行结果:
14:50:16: timer1 is sleeping 10 seconds
14:50:26: timer2 is sleeping 10 seconds
14:50:36: timer2 is sleeping 10 seconds
 
结果分析:
我的启动时间均是1秒以后,但是timer1和timer2启动的时间明显不一致

代码示例:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package timer_test;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class TimerTest
{
    private final Timer m_timer = new Timer();
    public static void main(String[] args)
      {
        new TimerTest().test();
    }
    public void test()
      {
        //问题一示例:
        m_timer.scheduleAtFixedRate(new TaskUseLongTime(), 1000, 5000);
        m_timer.scheduleAtFixedRate(new TaskNormal(), 5000, 3000);
        //问题二示例:
        //   m_timer.schedule(new TaskThrowException(), 1000);
        //   m_timer.schedule(new TaskNormal(), 2000);
        //问题三示例:
        //   m_timer.scheduleAtFixedRate(new TaskUseLongTime("timer1"), 1000, 5000);
        //   m_timer.scheduleAtFixedRate(new TaskUseLongTime("timer2"), 1000, 5000);
    }
    private class TaskUseLongTime extends TimerTask
      {
        private String m_taskName = "timer";
        public TaskUseLongTime(){
        }
        public TaskUseLongTime(String taskName)
            {
            m_taskName = taskName;
        }
        @Override
            public void run()
            {
            try
                  {
                System.out.println(getCurrentTime()+": "+m_taskName+" is sleeping 10 seconds");
                Thread.sleep(10000);
            }
            catch (InterruptedException e)
                  {
            }
        }
    }
    private class TaskNormal extends TimerTask
      {
        @Override
            public void run()
            {
            System.out.println(getCurrentTime()+": Task Normal executed");
        }
    }
    private class TaskThrowException extends TimerTask
      {
        @Override
            public void run()
            {
            System.out.println(getCurrentTime()+": Throw exception");
            throw new RuntimeException();
        }
    }
    private String getCurrentTime()
      {
        return new SimpleDateFormat("HH:mm:ss").format(new Date());
    }
}

2.ScheduleThreadPoolExecutor

ScheduleThreadPoolExecutor始于jdk1.5,是由DouLea先生编写的,其利用ThreadPoolExecutor和DelayQueue巧妙的结合完成了多线程定时器的实现,解决了Timer中由于单线程而导致的上述三个缺陷。

问题一中的问题是因为单线程顺序执行导致后续任务无法按时完成,我们看到多线程可以很容易的解决此问题,同时我们注意到TaskUseLongTime的执行时间为10s(请看后续代码),我们定时任务间隔是5秒,但是从结果中发现我们的任务执行间隔却是10秒,所以我们可以判断ScheduleThreadPoolExecutor是采用每线程每任务的模式工作的。

?
1
2
3
4
5
6
7
8
9
10
11
//问题一:
m_timer.scheduleAtFixedRate(new TaskUseLongTime(), 1000, 5000, TimeUnit.MILLISECONDS);
m_timer.scheduleAtFixedRate(new TaskNormal(), 1000, 5000, TimeUnit.MILLISECONDS);
 
运行结果:
14:54:37: Task Normal executed
14:54:37: timer is sleeping 10 seconds
14:54:42: Task Normal executed
14:54:47: Task Normal executed
14:54:47: timer is sleeping 10 seconds
14:54:52: Task Normal executed

问题二中我们发现当抛出异常的任务执行后不影响其他任务的运行,同时我们发现在运行结果里面没有将我们的异常抛出,这是因为ScheduleThreadPoolExecutor类在执行完定时任务后会返回一个ScheduledFuture运行结果,不论结果是顺利完成还是有异常均会保存在这里。

?
1
2
3
4
5
6
7
8
9
10
11
//问题二:
m_timer.scheduleAtFixedRate(new TaskThrowException(), 1000, 5000, TimeUnit.MILLISECONDS);
m_timer.scheduleAtFixedRate(new TaskNormal(), 1000, 5000, TimeUnit.MILLISECONDS);
 
运行结果:
14:58:36: Throw exception
14:58:36: Task Normal executed
14:58:41: Task Normal executed
14:58:46: Task Normal executed
14:58:51: Task Normal executed
14:58:56: Task Normal executed

问题三由于是多线程所以我们可以保证我们的定时任务可以同时执行

?
1
2
3
4
5
6
7
8
9
10
11
//问题三:
m_timer.scheduleAtFixedRate(new TaskUseLongTime("timer1"), 1000, 5000, TimeUnit.MILLISECONDS);
m_timer.scheduleAtFixedRate(new TaskUseLongTime("timer2"), 1000, 5000, TimeUnit.MILLISECONDS);
 
运行结果:
15:01:12: timer1 is sleeping 10 seconds
15:01:12: timer2 is sleeping 10 seconds
15:01:22: timer2 is sleeping 10 seconds
15:01:22: timer1 is sleeping 10 seconds
15:01:32: timer1 is sleeping 10 seconds
15:01:32: timer2 is sleeping 10 seconds

详细代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
package timer_test;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.Callable;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ScheduleThreadPoolExecutorTest
{
    private final ScheduledThreadPoolExecutor m_timer = new ScheduledThreadPoolExecutor(10);
    public static void main(String[] args)
      {
        ScheduleThreadPoolExecutorTest timerTest = new ScheduleThreadPoolExecutorTest();
        timerTest.test();
        try
            {
            Thread.sleep(100000);
        }
        catch (InterruptedException e)
            {
        }
        finally
            {
            timerTest.shutdown();
        }
    }
    public void shutdown()
      {
        m_timer.shutdown();
    }
    public void test()
      {
        //问题一:
        //   m_timer.scheduleAtFixedRate(new TaskUseLongTime(), 1000, 5000, TimeUnit.MILLISECONDS);
        //   m_timer.scheduleAtFixedRate(new TaskNormal(), 1000, 5000, TimeUnit.MILLISECONDS);
        //问题二:
        //   m_timer.scheduleAtFixedRate(new TaskThrowException(), 1000, 5000, TimeUnit.MILLISECONDS);
        //   m_timer.scheduleAtFixedRate(new TaskNormal(), 1000, 5000, TimeUnit.MILLISECONDS);
        //问题三:
        m_timer.scheduleAtFixedRate(new TaskUseLongTime("timer1"), 1000, 5000, TimeUnit.MILLISECONDS);
        m_timer.scheduleAtFixedRate(new TaskUseLongTime("timer2"), 1000, 5000, TimeUnit.MILLISECONDS);
    }
    private class TaskUseLongTime implements Callable<Integer>, Runnable
      {
        private String m_taskName = "timer";
        private TaskUseLongTime(){
        }
        private TaskUseLongTime(String taskName)
            {
            m_taskName = taskName;
        }
        public void run()
            {
            try
                  {
                System.out.println(getCurrentTime()+": "+m_taskName+" is sleeping 10 seconds");
                Thread.sleep(10000);
            }
            catch (InterruptedException e)
                  {
            }
        }
        public Integer call() throws Exception
            {
            run();
            return 0;
        }
    }
    @SuppressWarnings("unused")
      private class TaskNormal implements Callable<Integer>, Runnable
      {
        public Integer call() throws Exception
            {
            run();
            return 0;
        }
        public void run()
            {
            System.out.println(getCurrentTime()+": Task Normal executed");
        }
    }
    @SuppressWarnings("unused")
      private class TaskThrowException implements Callable<Integer>, Runnable
      {
        public Integer call() throws Exception
            {
            System.out.println(getCurrentTime()+": Throw exception");
            throw new RuntimeException();
        }
        public void run()
            {
            System.out.println(getCurrentTime()+": Throw exception");
            throw new RuntimeException();
        }
    }
    private String getCurrentTime()
      {
        return new SimpleDateFormat("HH:mm:ss").format(new Date());
    }
}

总结

以上就是本文关于浅谈java定时器的发展历程的全部内容,希望对大家有所帮助。如有不足之处,欢迎留言指出。感谢朋友们对本站的支持!

原文链接:http://hacksin.iteye.com/blog/2157572