Timer详解_酒醉梦醒的博客-程序员秘密_timer分析

技术标签: schedule  生产者消费者  定时器  并发编程  

timer介绍:

Timer是Josh Bloch在jdk1.3发布的一个新的api,主要用于做定时任务.

timer的使用:

1:schedule(TimerTask task, long delay) 在delay毫秒的延迟后执行task

2:schedule(TimerTask task, Date time) 在指定的time时间执行task

3:schedule(TimerTask task, long delay, long period) 在delay毫秒延迟后按照period的周期循环定时执行task

4:schedule(TimerTask task, Date firstTime, long period)在指定的firstTime时间开始按照period的周期循环定时执行task

5:scheduleAtFixedRate(TimerTask task, long delay, long period) 这个先理解为和3一样,后面会解释二者的区别

6:scheduleAtFixedRate(TimerTask task, Date firstTime, long period)这个先理解为和4一样,后面会解释二者的区别

6的实例:每天24点去执行定时任务

		Timer timer = new Timer();
        Calendar cal = Calendar.getInstance();
        cal.set(Calendar.HOUR_OF_DAY,24);
        cal.set(Calendar.MINUTE,0);
        cal.set(Calendar.SECOND,0);
        timer.scheduleAtFixedRate(new TimerTask() {
    
            @Override
            public void run() {
    
                System.out.println("执行任务");
            }
        },cal.getTime(),24*60*60*1000);

timer源码分析:

TimerThread :单线程/消费者/任务线程
TaskQueue :任务队列/优先队列/最小平衡堆/容器
我们写的定时任务是生产者,TimerThread 是消费者,可以看出这是一个单消费者多生产者的模型,而且这个线程还是采取轮询的方式来消费产品,这两个模型决定了Timer的上限。

	/**
     * timer的任务队列,这个队列共享给TimerThread ,timer.schedule...()生产任务,
     * TimerThread 消费任务,在适合的时候执行该任务,过时了则从队列移除
     */
    private final TaskQueue queue = new TaskQueue();
	//消费者线程
    private final TimerThread thread = new TimerThread(queue);

下面看生产者的核心方法,所有生产者最终都会走到这个方法

生产者代码

private void sched(TimerTask task, long time, long period) {
    
        if (time < 0)
            throw new IllegalArgumentException("Illegal execution time.");
        //对周期做一下限制 防止溢出
        if (Math.abs(period) > (Long.MAX_VALUE >> 1))
            period >>= 1;
		
        synchronized(queue) {
    
            if (!thread.newTasksMayBeScheduled)
                throw new IllegalStateException("Timer already cancelled.");

            synchronized(task.lock) {
    
                if (task.state != TimerTask.VIRGIN)
                    throw new IllegalStateException(
                        "Task already scheduled or cancelled");
                task.nextExecutionTime = time;//这个任务下一次执行时间
                task.period = period;//执行周期
                task.state = TimerTask.SCHEDULED;//任务状态
            }
            queue.add(task);//加入最小平衡堆
            if (queue.getMin() == task)//如果堆顶任务就是刚加进去的任务
                queue.notify();//唤醒堆顶任务
        }
    }

任务状态

	//This task has not yet been scheduled.
	//处女,任务还没有被调度,默认是这个
	static final int VIRGIN = 0;
	//任务被调度,但是未执行,就是说在队列等待调度
	static final int SCHEDULED   = 1;
	//已经执行了,或者正在执行
	static final int EXECUTED    = 2;
	//任务被取消with a call to TimerTask.cancel
	static final int CANCELLED   = 3;

queue.add(task)代码

	void add(TimerTask task) {
    
        // 容器初始大小128,以2为指数扩容
        if (size + 1 == queue.length)
            queue = Arrays.copyOf(queue, 2*queue.length);
		//size初始为0,可以看出queue的下标0被舍弃掉了,直接从下标1开始入堆
		//这样一来i的左孩子就是2*i了,右孩子是2*i+1.
        queue[++size] = task;
        fixUp(size);//加入堆中后 可能不是最小堆,所以需要对堆做一次fixup调整为最小平衡堆
    }

fixUp(size)代码

 	private void fixUp(int k) {
    
        while (k > 1) {
    
            int j = k >> 1;
            if (queue[j].nextExecutionTime <= queue[k].nextExecutionTime)
                break;
            TimerTask tmp = queue[j];  queue[j] = queue[k]; queue[k] = tmp;
            k = j;
        }
    }

这段代码的意思其实就是把刚刚加入堆的任务安排到合适的地方去。直接将代码不好讲,还是看个实例:没有学过堆的同学建议花两小时学一下,不然可能听不懂。假设某个时刻堆的任务是下图所示,2,3,4这些数字代表nextExecutionTime(下次执行的时间),数字越小说明任务优先级越高。
2
/ \
3 4
某个时刻来了一个nextExecutionTime=1的任务,此时堆中任务如下图所示。
2(下标1)
/ \
3 4(下标3)
/
1(下标4)
很明显这已经不是一个最小堆了,我们需要把1往上调整。
现在来看代码:
第一次循环
k=4,j=2
if(3<=1)break;这里不成立,但是如果来了一个任务的nextExecutionTime>=3 这里会直接break掉,因为已经是最小堆
交换1和3
2(下标1)
/ \
1 4(下标3)
/
3(下标4)
k=2
第二次循环
k=2 ,j =1
if(2<=1)break;这里不成立
交换1和2
1(下标1)
/ \
2 4(下标3)
/
3(下标4)
k=1退出循环,已经是最小堆
至此生产者的代码就看完了

消费者代码

	//单消费者
	private void mainLoop() {
    
        while (true) {
    //轮询模式
            try {
    
                TimerTask task;
                boolean taskFired;
                synchronized(queue) {
    
                    // Wait for queue to become non-empty
                    while (queue.isEmpty() && newTasksMayBeScheduled)
                        queue.wait();
                    if (queue.isEmpty())
                        break; // Queue is empty and will forever remain; die

                    // Queue nonempty; look at first evt and do the right thing
                    //这两个时间及其重要 
                    //scheduleAtFixedRate和schedule的区别就体现在这两个时间和+-period上面
                    long currentTime, executionTime;
                    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;//执行时间
                        //当前时间>=执行时间 才去执行任务
                        if (taskFired = (executionTime<=currentTime)) {
    
                        	//一次性的定时任务
                            if (task.period == 0) {
     // Non-repeating, remove
                                queue.removeMin();//移除堆顶并进行一次调整
                                task.state = TimerTask.EXECUTED;//任务标记为执行状态
                            } else {
     // Repeating task, reschedule 周期任务
                            	//这里的代码及其经典够味
                            	//schedule的period传的是-period
                            	//scheduleAtFixedRate的period是+period
                            	//如果是schedule调度,下一次执行时间改为
                            	// currentTime-task.period(当前时间-(-period))
                            	//这里看出来schedule是依据当前时间来调度的
                            	//如果是scheduleAtFixedRate调度,下一次执行时间是
                            	//executionTime + task.period,
                            	//这里看出来scheduleAtFixedRate是依据执行时间调度的
                            	//(这个执行时间是我们写代码指定的那个时间)							
                            	//并且while(true){}保证scheduleAtFixedRate这种调度方式会自动补上之前缺失的任务。
                                queue.rescheduleMin(
                                  task.period<0 ? currentTime   - task.period
                                                : executionTime + task.period);
                            }
                        }
                    }
                    if (!taskFired) // Task hasn't yet fired; wait
                        queue.wait(executionTime - currentTime);
                }
                if (taskFired)  // Task fired; run it, holding no locks
                    task.run();
            } catch(InterruptedException e) {
    
            }
        }
    }
}

queue.removeMin();代码

	void removeMin() {
    
        queue[1] = queue[size];//堆顶置为堆尾
        queue[size--] = null;  //堆尾置为null,size--
        fixDown(1);//对堆顶进行一次调整,和fixup反着来,目的都是为了调整成最小堆
    }

timer的schedule和scheduleAtFixedRate区别:

		Timer timer = new Timer();
        SimpleDateFormat fTime = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
        Date date = fTime.parse("2019/7/3 10:50:00");
        timer.scheduleAtFixedRate(new TimerTask(){
    
            public void run()
            {
    
                System.out.println("exec task");
            }
        },date,3*60*1000);

程序指定运行时间是2019/7/3 10:50:00,每隔三分钟运行一次
如果我等到2019/7/3 10:55:00 去运行这段程序,即已经过了五分钟了。

使用scheduleAtFixedRate会快速打印两个exec task(第一次2019/7/3 10:50:00,第二次2019/7/3 10:53:00),然后按照2019/7/3 10:56:00–> 2019/7/3 10:59:00这样打印下去。也就是说scheduleAtFixedRate是按照指定的时间开始算,如果程序运行的时间晚于这个指定时间,他会一次性补上之前的任务,然后按照间隔时间去执行。

如果使用schedule他不会补上之前的任务,而且他是按照实际执行程序的时间开始算,也就是说如果2019/7/3 10:55:00用 去schedule运行这段程序那么下一次打印时间将是2019/7/3 10:58:00.

timer的缺点:

缺点一:对于耗时任务及多任务非常不友好

	Timer timer = new Timer();
	final long start = System.currentTimeMillis();
        timer.schedule(new TimerTask() {
    
            @Override
            public void run() {
    
                System.out.println("time1:"+ (System.currentTimeMillis()-start));
                try {
    
                    //模拟耗时操作
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
    
                    e.printStackTrace();
                }
            }
        },1000);

        timer.schedule(new TimerTask() {
    
            @Override
            public void run() {
    
                System.out.println("time2:"+ (System.currentTimeMillis()-start));
            }
        },2000);

我希望的结果是
time1:1001
time2:2001
可实际上由于单线程的原因结果是
time1:1001
time2:4001
由这个例子看出Timer只适用于耗时短的单任务。

缺点二:对于运行时异常不友好

Timer timer = new Timer();
	final long start = System.currentTimeMillis();
        timer.schedule(new TimerTask() {
    
            @Override
            public void run() {
    
                System.out.println("time1:"+ (System.currentTimeMillis()-start));
                throw new RuntimeException();
            }
        },1000);

        timer.schedule(new TimerTask() {
    
            @Override
            public void run() {
    
                System.out.println("time2:"+ (System.currentTimeMillis()-start));
            }
        },2000);

在timer1里抛运行时异常会导致time2不可用,这一点问题不大,我们有最佳实践:在run里面手动catch异常进行处理。

timer的替代产品:

相比于timer,在jdk1.5的时候,Doug Lea老先生主笔写了juc新api,线程池。其中的带有调度功能的线程池就可以执行定时任务,而且性能及稳定性更优秀。线程池将在下篇博文讲到。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/LiuRenyou/article/details/94552916

智能推荐

【刷题】紫书第六章:数据结构基础(例题部分)_紫书数据结构例题_Little_Fall的博客-程序员秘密

还没写完,但上课实在写不了题,总结一下。总结map的operator[]操作会在目标元素不存在时新建一个元素,无副作用的检验元素是否存在应该使用map.count()跳过了本章指针建树与手动内存池的部分,以后再学。多组数据题,完全可以while(init()),init包括了初始化与读入工作。list.insert(iter,val),在iter左边插入,不改变iter,返回被插入位置...

科技新闻_每日一闻_珞喻小森林的博客-程序员秘密

内容提要:谷歌员工联名抗议谷歌正在秘密进行一个项目(代号:Dragonfly)-该项目准备为适应中国政府的审查与监管机制而对谷歌搜索引擎得到的结果进行修改后再呈现给中国用户。评论:个人感觉谷歌员工只是要求公司对其公开项目背景,让他们知道自己在做什么?希望谷歌尊重员工个人的偏好与道德选择,让其自己决定做还是不做这个项目,谷歌高智商的码农们也不是那么好欺骗的呀!强行把中国政府要求谷歌搜索引擎做...

yum install rz sz_mengxianhua的博客-程序员秘密

yum自动安装:yum install lrzsz手动安装方法如下:定制安装的linux可能没有把rzsz包安装到系统,这对用securecrt这样的windows工具传输文件特别不方便。为了使用这个方便的法门,可以手动安装之。1、 下载软件 rzsz-3.48.tar.gz。登录linux,用命令wget http://freeware.sgi.com/s

LVS NAT模式和DR模式的区别_linuxvfast的博客-程序员秘密

2019独角兽企业重金招聘Python工程师标准&gt;&gt;&gt; ...

基于Linux+6818开发板实现普通电子相册翻页功能_基于粤嵌开发板的电子相册系统框图_佳佳鸽的博客-程序员秘密

更多资料请点击:我的目录本篇仅用于记录自己所学知识及应用,代码仍可优化,仅供参考,如果发现有错误的地方,尽管留言于我,谢谢。首先是外部进程传参,传进的是某目录文件的路径(绝对路径/相对路径)。接着打开目录文件,遍历目录内所有的文件,将后缀名为“.bmp”的普通文件全部找出来,并拼接保存它们的路径到双向循环链表里,一个节点存放一个bmp图片的路径。再通过触摸屏返回的坐标值进行逻辑判断,例如返回坐标值 &lt; 400,触摸了左边的显示屏,我们应该让双向循环链表的当前节点 p 指向 p-&gt;prev节点

从“程序员转行卖烧饼”想到IT人创业_乱战狂歌的博客-程序员秘密

作者:杨中科,传智播客联合创始人、如鹏网发起人。现为传智播客教学总监。原帖:http://bbs.csdn.net/topics/390579896  我的一个朋友最近总在跟我念叨着“我不想做开发了,整天累死累活写程序,也攒不下几个钱。我想辞职搞点啥!”我问他:“你想搞点啥?”。他说:“搞啥都比做开发强,做个网站赚广告费,接私活……实在不行我去卖烧饼去,你没看到《网

随便推点

C# sleep 和wait的区别_卓月的博客-程序员秘密

sleep和wait都是使线程暂时停止执行的方法,但它们有很大的不同。1. sleep是线程类Thread 的方法,它是使当前线程暂时睡眠,可以放在任何位置。而wait,它是使当前线程暂时放弃对象的使用权进行等待,必须放在同步方法或同步块里。2.Sleep使用的时候,线程并不会放弃对象的使用权,即不会释放对象锁,所以在同步方法或同步块中使用sleep,一个线程访问时,其他的线程

数据仓库--事实表和维度表_事实表,不设置主键,最后两项是measure属性_大灰狼学编程的博客-程序员秘密

文章参考:https://blog.csdn.net/davidwang9527/article/details/255531171.数据仓库与操作型数据库的区别数据仓库的物理模型与常见的操作型数据库的物理模型有很大不同。最明显的区别是:操作型数据库主要是用来支撑即时操作,对数据库的性能和质量要求都比较高,为了防止“garbage in,garbage out”,通常设计操作型数据库的...

分布式之分布式事务、分布式锁、接口幂等性、分布式session_weixin_30550081的博客-程序员秘密

一、分布式session  session 是啥?浏览器有个 cookie,在一段时间内这个 cookie 都存在,然后每次发请求过来都带上一个特殊的jsessionid cookie,就根据这个东西,在服务端可以维护一个对应的 session 域,里面可以放点数据。  一般的话只要你没关掉浏览器,cookie 还在,那么对应的那个 session 就在,但是如果 cookie 没了,s...

蓝屏代码大全详解_散格-的博客-程序员秘密

完整的BSOD错误代码列表从STOP 0x1到STOP 0xC0000221一个死机(BSOD)的蓝屏,技术上称为一个STOP错误,若在Windows遭受了严重的错误,被迫“停”的问题。在任何Windows操作系统中都会出现BSOD错误,包括Windows 10,Windows 8,Windows 7,Windows Vista,Windows XP甚至Windows 98/95。由于蓝屏错误让您别无选择,只能重新启动,故障排除可能很困难。幸运的是,几乎每个STOP错误都包含一个基于十六进制的.

Kettle调用Java文件(Jar包)_kettle调用jar包传参_俊不见高堂明镜的博客-程序员秘密

Kettle的脚本–>Modified Java Script Value不仅可以写js代码来处理数据,也可利用这个组件调用已经写好的Jar文件。 第一步、准备java项目。 在IDE中新建java项目,并写好相应的处理逻辑。 将写好的java项目,导出成jar包,放到kettle的lib或者libext文件夹内(注意:项目内引用的jar包,若kettle中不存在,也要一并复制进去)。

QQ5.0列表滑动删除的简单实现_古隔空间i believe_纯洁小码农_z的博客-程序员秘密

自定义一个view 继承LinearLayout:代码:public class SwipeLayout extends LinearLayout { private ViewDragHelper viewDragHelper; private View contentView; private View actionView; private int

推荐文章

热门文章

相关标签