Timer详解

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,线程池。其中的带有调度功能的线程池就可以执行定时任务,而且性能及稳定性更优秀。线程池将在下篇博文讲到。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值