java 计时器怎么用(java实现定时器的方式)

Java在1.3版本引入了Timer工具类,它是一个古老的定时器,搭配TimerTask和TaskQueue一起使用。从Java5开始在并发包中引入了另一个定时器
ScheduledThreadPoolExecutor,它对Timer做了很多改进并提供了更多的工具,可以认为是对Timer的取代。

那为什么还要介绍Timer工具类呢?通过了解Timer的功能和它背后的原理,有助于我们更好的对比了解
ScheduledThreadPoolExecutor,同时ScheduledThreadPoolExecutor的一些改进思想在我们平时的编码工作中也可以借鉴。

主要成员变量

Timer中用到的主要是两个成员变量:

  1. TaskQueue:一个按照时间优先排序的队列,这里的时间是每个定时任务下一次执行的毫秒数(相对于1970年1月1日而言)
  2. TimerThread:对TaskQueue里面的定时任务进行编排和触发执行,它是一个内部无限循环的线程。
//根据时间进行优先排序的队列    
private final TaskQueue queue = new TaskQueue();

//消费线程,对queue中的定时任务进行编排和执行
private final TimerThread thread = new TimerThread(queue);

//构造函数
public Timer(String name) {
        thread.setName(name);
        thread.start();
}

定时功能

Timer提供了三种定时模式:

  1. 一次性任务
  2. 按照固定的延迟执行(fixed delay)
  3. 按照固定的周期执行(fixed rate)

第一种比较好理解,即任务只执行一次;针对第一种,Timer提供了以下两个方法:

//在当前时间往后delay个毫秒开始执行
public void schedule(TimerTask task, long delay) {...}
//在指定的time时间点执行
public void schedule(TimerTask task, Date time) {...}

第二种Fixed Delay模式也提供了以下两个方法

//从当前时间开始delay个毫秒数开始定期执行,周期是period个毫秒数
public void schedule(TimerTask task, long delay, long period) {...}
////从指定的firstTime开始定期执行,往后每次执行的周期是period个毫秒数
public void schedule(TimerTask task, Date firstTime, long period){...}

它的工作方式是:

第一次执行的时间将按照指定的时间点执行(如果此时TimerThread不在执行其他任务),如有其他任务在执行,那就需要等到其他任务执行完成才能执行。

从第二次开始,每次任务的执行时间是上一次任务开始执行的时间加上指定的period毫秒数。

如何理解呢,我们还是看代码

public static void main(String[] args) {
        TimerTask task1 = new DemoTimerTask("Task1");
        TimerTask task2 = new DemoTimerTask("Task2");
        Timer timer = new Timer();
        timer.schedule(task1, 1000, 5000);
        timer.schedule(task2, 1000, 5000);
}
    
static class DemoTimerTask extends TimerTask {
        private String taskName;
        private DateFormat df = new SimpleDateFormat("HH:mm:ss---");
        
        public DemoTimerTask(String taskName) {
            this.taskName = taskName;
        }
        
        @Override
        public void run() {
            System.out.println(df.format(new Date()) + taskName + " is working.");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println(df.format(new Date()) + taskName + " finished work.");
        }
}

task1和task2是几乎同时执行的两个任务,而且执行时长都是2秒钟,如果此时我们把第六行注掉不执行,我们将得到如下结果(和第三种Fixed Rate模式结果相同):

13:42:58---Task1 is working.
13:43:00---Task1 finished work.
13:43:03---Task1 is working.
13:43:05---Task1 finished work.
13:43:08---Task1 is working.
13:43:10---Task1 finished work.

如果打开第六行,我们再看下两个任务的执行情况。我们是期望两个任务能够同时执行,但是Task2是在Task1执行完成后才开始执行(原因是TimerThread是单线程的,每个定时任务的执行也在该线程内完成,当多个任务同时需要执行时,只能是阻塞了),从而导致Task2第二次执行的时间是它上一次执行的时间(13:43:57)加上5秒钟(13:44:02)。

13:43:55---Task1 is working.
13:43:57---Task1 finished work.
13:43:57---Task2 is working.
13:43:59---Task2 finished work.
13:44:00---Task1 is working.
13:44:02---Task1 finished work.
13:44:02---Task2 is working.
13:44:04---Task2 finished work.

那如果此时还有个Task3也是同样的时间点和间隔执行会怎么样呢?

结论是:也将依次排队,执行的时间依赖两个因素:

1.上次执行的时间

2.期望执行的时间点上有没有其他任务在执行,有则只能排队了


我们接下来看下第三种Fixed Rate模式,我们将上面的代码稍作修改:

public static void main(String[] args) {
        TimerTask task1 = new DemoTimerTask("Task1");
        TimerTask task2 = new DemoTimerTask("Task2");
        
        Timer timer = new Timer();
        timer.scheduleAtFixedRate(task1, 1000, 5000);
        timer.scheduleAtFixedRate(task2, 1000, 5000);
}
    
static class DemoTimerTask extends TimerTask {
        private String taskName;
        private DateFormat df = new SimpleDateFormat("HH:mm:ss---");
        
        public DemoTimerTask(String taskName) {
            this.taskName = taskName;
        }
        
        @Override
        public void run() {
            System.out.println(df.format(new Date()) + taskName + " is working.");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println(df.format(new Date()) + taskName + " finished work.");
        }
}

Task1和Task2还是在相同的时间点,按照相同的周期定时执行任务,我们期望Task1能够每5秒定时执行任务,期望的时间点是:14:21:47-14:21:52-14:21:57-14:22:02-14:22:07,实际上它能够交替着定期执行,原因是Task2也会定期执行,并且对TaskQueue的锁他们是交替着拿的(这个在下面分析TimerThread源码的时候会讲到)

14:21:47---Task1 is working.
14:21:49---Task1 finished work.
14:21:49---Task2 is working.
14:21:51---Task2 finished work.
14:21:52---Task2 is working.
14:21:54---Task2 finished work.
14:21:54---Task1 is working.
14:21:56---Task1 finished work.
14:21:57---Task1 is working.
14:21:59---Task1 finished work.
14:21:59---Task2 is working.
14:22:01---Task2 finished work.

TimerThread

上面我们主要讲了Timer的一些主要源码及定时模式,下面我们来分析下支撑Timer的定时任务线程TimerThread。

TimerThread大概流程图如下:

java 计时器怎么用(java实现定时器的方式)

TimerThread流程

源码解释如下:

private void mainLoop() {
        while (true) {
            try {
                TimerTask task;
                boolean taskFired;
                synchronized(queue) {
                    // 如果queue里面没有要执行的任务,则挂起TimerThread线程
                    while (queue.isEmpty() && newTasksMayBeScheduled)
                        queue.wait();
                    // 如果TimerThread被激活,queue里面还是没有任务,则介绍该线程的无限循环,不再接受新任务
                    if (queue.isEmpty())
                        break; 

                    long currentTime, executionTime;
                    // 获取queue队列里面下一个要执行的任务(根据时间排序,也就是接下来最近要执行的任务)
                    task = queue.getMin();
                    synchronized(task.lock) {
                        if (task.state == TimerTask.CANCELLED) {
                            queue.removeMin();
                            continue;  // No action required, poll queue again
                        }
                        currentTime = System.currentTimeMillis();
                        executionTime = task.nextExecutionTime;
                        // taskFired表示是否需要立刻执行线程,当task的下次执行时间到达当前时间点时为true
                        if (taskFired = (executionTime0表示是fixed rate模式的任务
                                queue.rescheduleMin(
                                  task.period

结论

通过上面的分析,我们可以得出以下结论:

  1. Timer支持三种模式的定时任务(一次性任务,Fixed Delay模式,Fixed Rate模式)
  2. Timer中的TimerThread是单线程模式,因此导致所有定时任务不能同时执行,可能会出现延迟
  3. TimerThread中并没有处理好任务的异常,因此每个TimerTask的实现必须自己try catch防止异常抛出,导致Timer整体失效

Demo代码位置

秒鲨号所有文章资讯、展示的图片素材等内容均为注册用户上传(部分报媒/平媒内容转载自网络合作媒体),仅供学习参考。用户通过本站上传、发布的任何内容的知识产权归属用户或原始著作权人所有。如有侵犯您的版权,请联系我们反馈!本站将在三个工作日内改正。
(0)

大家都在看

  • 996马云什么意思(马云:996是你们的福报(转载))

    “今天中国BAT(百度、阿里、腾讯)这些公司能够996,我认为是我们这些人修来的福报。” 4月11日,阿里巴巴董事局主席马云在内部交流中对员工谈及近期备受争议的996加班文化。马云…

    2022年6月5日
  • 2021广州基本工资是多少(各地最新调整的最低工资表)

    截至2021年6月5日,已有6个省、自治区调整了2021年的最低工资标准,分别是黑龙江、江西、新疆、陕西、天津、西藏。 其他25省(自治区、直辖市)中: 有7省执行2017年的标准…

    2022年10月14日
  • 房贷还款方式哪种好(怎样还房贷款最划算)

    在这个贷款买房的时代,购房者都会成为苦逼的房奴,而他们都需要面临一个问题,那就是房贷还款方式到底选择哪一种最好呢?对于这个问题,其实并没有准确的答案,因为不同的还款方式一般都会有着…

    2022年6月7日
  • 现在开个什么加工厂比较好(自己开个小型加工厂哪种最赚钱)

    作为传统的农业大国,秸秆在中国的利用有着悠久历史。杜绝秸秆焚烧,终结遍地狼烟,而是将秸秆综合利用、资源再生,一直是无数循环资源人的梦想。 秸秆,如何跳出农业这个单一循环圈,走向凤凰…

    2022年2月15日
  • 冬至祝福语(最美祝福送上)

    冬至简短祝福语八个字 12月21日下午6时02分,将迎来“冬至”节气。这也是北半球一年中白天最短、夜晚最长的一天。从“冬至”开始,就要进入“数九寒天”时节了,民间俗称“进九”。“气…

    2022年8月26日
  • 信念的含义是什么(深度剖析信念的真正含义)

    第一讲 思考题: 1.理想、信念的含义、特征及理想信念的意义 理想的含义:理想是人们在实践中形成的,有实现的可能性,对未来社会和自身发展目标的向往与追求,是人们的世界观,人生观和价…

    2022年8月7日
  • 单用途商业预付卡管理办法(单用途预付卡管理办法解读)

    昨日,张店区消费者权益保护委员会发布的预付式消费调研报告中显示,目前,预付式消费在美容美发、教育培训、健身养生、洗车等服务消费领域多见,其他行业也不乏有预付式消费模式。但由于预付式…

    2022年6月3日
  • 失业保险金领取几个月(失业保险金领取多少钱)

    失业金是劳动者失业以后的最低生活保障,当然失业的种类主要有主动失业和被动失业,所谓的主动失业就是自己辞职,而被动失业的原因则非常多了,像用人单位经济性裁员、用人单位与劳动者终止合同…

    2022年7月2日 投稿
  • 免费psd素材网站有哪些(免费素材PSD格式)

    众所周知,设计师每天面对最多的图形文件格式就是PSD了,如果一些好的PSD素材可以帮助自己事半功倍地完成设计工作,何乐而不为?加上自己的创意和简单调整PSD模板为自己所用,节省大量…

    2022年6月9日 投稿
  • 眼镜店怎么开(眼镜店怎么赚钱)

    最近,有不少人询问如何开眼镜店,对于新手来说,大多就是听人说眼镜店比较赚钱,就想着开眼镜店了。实际上并没有那么简单,还是有不少问题和困难存在的。现在,就为大家系统整理自己开店和加盟…

    2022年6月12日 投稿
品牌推广 在线咨询
返回顶部