【java多线程】编程核心技术5Timer + Java并发编程:Timer和TimerTask(转载)

像作者所说,本博文将总结如下内容

      1、如何实现指定时间执行任务;2、如果实现按指定周期执行任务(定时器的常见需求)

Timer用于设置计划任务,但封装任务的类型是TimerTask类,执行任务的代码要放入TimerTask子类中,你猜为何

 

schedule(***)*情况下指定任务执行

       

//delay后调度一个task、一次
public void schedule(TimerTask task, long delay);
//源码
public void schedule(TimerTask task, long delay) {
        if (delay < 0)
            throw new IllegalArgumentException("Negative delay");
        /**
         * 参数二:如果传入Date,对象.getTime即可
         * 参数三:0,时间片
         */
        sched(task,System.currentTimeMillis()+delay,0);
}

//在指定的时间点time上调度一个task,一次
public void schedule(TimerTask task, Date time);

//delay后开始调一个task,每次调完最少等待period(ms)才开始调度
public void schedule(TimerTask task, long delay, long period);

public void schedule(TimerTask task, long delay, long period) {
        if (delay < 0)
            throw new IllegalArgumentException("Negative delay.");
        if (period <= 0)
            throw new IllegalArgumentException("Non-positive period.");
        /*参数三:-取反*/
        sched(task, System.currentTimeMillis()+delay, -period);
    }



//同上,delay为第一次调度的时间:当前时间+时间片 实际时间 根据情况变动但是可能会被少调度*次
public void schedule(TimerTask task, Date firstTime, long period);

//在delay后开始调task,每经过period再次调度:计算出现在应该执行的时间+时间片 理论时间 时间不变减少漏掉调度的情况
public void scheduleAtFixedRate(TimerTask task, long delay, long period);

//同上,第一次调度的时间设为firstTime
public void scheduleAtFixedRate(TimerTask task, Date firstTime,long period);
//和上面的第二个源码并无上面差别
public void scheduleAtFixedRate(TimerTask task, long delay, long period) {
       if (delay < 0)
           throw new IllegalArgumentException("Negative delay.");
       if (period <= 0)
           throw new IllegalArgumentException("Non-positive period.");
       sched(task, System.currentTimeMillis()+delay, period);
}

 

都看到了吧,最终的落脚点在sche这个方法上,这个和lock比较像哈,看一下sched本尊:简单来说 将task放入队列queue

private void sched(TimerTask task,long time,long period){
        if(time<0)
            throw new IllegalStateException("Timer already cancelled");
        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 cancelled");
                //下一次执行时间
                task.nextExecutionTime = time;
                //时间片
                task.period = period;
                //状态
                task.state = TimerTask.SCHDULED;
            }
            //放入队列
            queue.add(task);
            if(queue.getMin()==task){
                //做一次notify操作
                queue.notify();
            }
        }
    }

queue属性的结果TaskQueue:

class TaskQueue {

    //内部task个数不超过128 不会扩容
    private TimerTask[] queue = new TimerTask[128];
 
    private int size = 0;

//内部提供了
/*
   1、add(TimerTask)增加一个任务、
   2、size()任务队列的长度、
   3、getMin()获取当前排序后最近需要执行的任务,下标为1,队列头部0不做任何操作、
   4、get(int)获取指定下标的数据,包括下标0、
   5、removeMin()删除当前最近执行的任务,即第一个元素,通常只调度一次的任务,执行完调用此方法,可以将TimerTask从队列中移除、
   6、quickRemove(int)删除指定元素,只有在Timer发送purge(清除)且当对应的
   7、TimerTask调用cancel才会被调用这个方法:即取消每个TimerTask后从队列中移除(任务在执行仍执行不过队列中被移除)这个cancel是TimerTask的,这个方法完成后将队列最后一个元素补充到这个位置,可能造成顺序不一致问题:后面方法会回补、
   8、rescheduleMin(long newTime)重新设置当前任务的下一次执行时间,并在队列中将其重新排序到合适的位置,调用fixDown、
   9、isEmpty()、clear()、
   10、fixUp()当新增task时,先将元素放在队列的尾部 向前找是否有比自己还要晚执行的任务 人将两个任务的顺序进行交换、
   11、fixDown()与Up相反,执行完一个任务后,加上一个时间片得到下一次的执行时间,将其顺序与后面任务进行对比、
   12、heapify()队列的后半截,做一次fixeDown,回补quickRemove方法,大量qR顺序被打乱、将一半的区域做一次简单排序即可
*/


     /**
     * 找到一个合适的位置来交换
     * 每次移动一个二进制位,找到合适的位置放下
     * 顺序未必有序,只需要看到距离调度部分近的是有序性强的:一定顺序性
     * @param k
     */
    private void fixDown(int k) {
        int j;
        // << 左移运算符
        while ((j = k << 1) <= size && j > 0) {
            if (j < size && queue[j].nextExecutionTime > queue[j + 1].nextExecutionTime)
                j++;
            if (queue[k].nextExecutionTime <= queue[j].nextExecutionTime)
                break;
            TimerTask tmp = queue[j];
            queue[j] = queue[k];
            queue[k] = tmp;
            k = j;
        }
    }

 

源码还有,继续拷贝,不过为了视觉,重来一个代码框吧,宝宝如此周到、心细,O(∩_∩)O哈哈~

//还是上面的类
public void cancel() {
        synchronized(queue) {
            thread.newTasksMayBeScheduled = false;
            queue.clear();
            queue.notify();  // In case queue was already empty.
        }
}

//对*Task做cancle操作后,通purge回收这些cancel掉的类空间,顺序会乱
public int purge() {
         int result = 0;
 
         synchronized(queue) {
             for (int i = queue.size(); i > 0; i--) {
                 if (queue.get(i).state == TimerTask.CANCELLED) {
                     queue.quickRemove(i);
                     result++;
                 }
             }
 
             if (result != 0)
                 queue.heapify();//重排序
         }
         return result;
     }

调度、notify、情空队列:TimerThread:内部属性newTasksMayBeScheduled:

//通过queue通过构造方法传入,Timer传递给这个线程的
private TaskQueue queue;

public void run() {
   try {
        mainLoop();//主循环程序
    } finally {
        synchronized(queue) {
            newTasksMayBeScheduled = false;
            queue.clear();  // Eliminate obsolete references
         }
     }
}

几乎要把海子大神的博文给搬过来了,看来这篇躲避不了被标为“转载”的命运了,本来是奔着“原创”抄、不是、写的,原创啊原创,下次、诶~再说何时见吧

 private void mainLoop() {
        //死循环,除非遇到不能捕获的异常或break才会跳出
        while (true) {
            try {
                TimerTask task;
                boolean taskFired;
                synchronized (queue) {
                    //跳出循环的条件:队列不为空,newTasksMayBeScheduled为false
                    while (queue.isEmpty() && newTaskMayBeScheduled)
                        queue.wait;//等待其他地方对queue发生notify操作
                    if (queue.isEmpty())
                        //newTasksMayBeScheduled=false则跳出
                        // 就是调用cancle,queue空、直接跳出外部死循环
                        break;//如果下面的任务还在跑 cancle不起作用
                    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;
                        //计算右边的值给taskFired,这个拆开更好看一些
                        if (taskFired = (executionTime <= currentTime)) {
                            if (task.period == 0) {//时间片
                                //no repeating ,remove
                                queue.removeMin();
                                task.state = TimerTask.EXECUTED;
                            } else {
                                //repeatring task,reschedule 重新设置最新时间并排序
                                queue.rescheduleMin(
                                        //period负,按照当前系统时间+时间片计算下一次时间
                                        //schedule和scheduleAtFixedRate的区别,内部通过正负数判定
                                        task.period < 0 ? currentTime - task.period :
                                                executionTime + task.period);
                            }
                        }
                    }
                    if (!taskFired)
                        //任务执行时间还未到,等待task hasn't yet fired ; wait
                        //可能会被其他线程操作add cancel 唤醒 因内部notify 时间不完全准确
                        queue.wait(executionTime - currentTime);
                }
                if(taskFired)
                    //线程需要,调用run:Task fired run it holding no locks
                    //TimerTask 不是多线程的run ,虽实现了Runnable 仅为了表示其实可执行的
                    task.run();
            }catch (InterruptedException e){}
        }
    }

从上面的代码中可以发现,当发生add、cancel以及在threadReaper调用finalize方法的时候会被调用

     发生add的时候(当队列还是空的时候)使得队列不为空就跳出循环

     cancle设置了状态、否则不会进入这个循环

     finalize()是Object中的方法,当垃圾回收器将要回收对象所占内存之前被调用,即当一个对象被虚拟机宣告死亡时会先调用它finalize()方法,让此对象处理它生前的最后事情(这个对象可以趁这个时机挣脱死亡的命运)

 

创建Timer的源码

//class TimerThread extends Thread {……}Timer内部包装了一个线程,用来做
//独立于外部线程的调度;TimerThread是个default类型的,默认情况下引用不
//到,被Timer自己使用
private TimerThread thread = new TimerThread(queue);


//队列:要调度的任务
private TaskQueue queue = new TaskQueue();


/*属性threadReaper,Object类型,只是重写了finalize,为了垃圾回收的时候,将相应的信息回收掉,做GC的回补:当timer由某种原因死掉而未被cancel时,清空队里中信息*/


// 通过Tiemer为前缀构造一个线程名称,不是主线程,主线程结束后timer自动结束
public Timer{
    this("Time="+serialNumber());
}


//传入是否为守护进程,守护进程当且仅当进程结束时,自动注销掉
public Timer(boolean isDaemon){
    this("Timer-"+serialNumber(),isDaemon);
}


public Timer(String name, boolean isDaemon) {
      thread.setName(name);
      thread.setDaemon(isDaemon);
      thread.start();
}


public Timer(String name){
    thread.setName(name);
    /*创建新线程,不是守护线程,一直运行*/
    thread.start();
}

将新创建的Timer改成守护线程:

TimerTask:队列方式一个一个顺序执行

        timerTask只是实现了run方法的一个类,具体的TimerTask处理逻辑需要自己来实现

        

//20s后执行,每秒执行一次,可以实现多个TimerTask
Timer timer = new Timer();
timer.schedule(new TimerTask() {
        public void run() {
            System.out.println("abc");
        }
}, 200000 , 1000);

注意:

        1、计划时间早于当前时间,则理解执行任务

 

Timer和TimerTask的简单组合不是多线程:

         一个Timer内部包装了一个Thread和一个Task队列,这个队列按照一定的方式将任务排队处理,包含的线程在Timer的构造方法调用时被启动,这个Thread的run无限循环这个task队列,若队列为空且没有发生cancel、将一直等待,如果等待完成、队列还是空的,则认为cancel了跳出死循环,结束任务,循环中如果发现任务需要执行的时间小于系统时间,则需要执行:根据任务的时间片重新计算下次执行时间,若时间片为0代表只执行一次,直接移除队列即可

      但可以实现多线程,任何东西是否是多线程完全看个人意愿,多个Timer自然是多线程,每个Timer有自己的处理逻辑,当然Timer从这里来看并不是很适合很多任务短时间内的快速调度;在多线程领域我们更多用的是多线程的Executors.newScheduledThreadPool,完成对调度队列中线程池的处理,内部通过

new ScheduledThreadPoolExecutor来创建线程池的Executor的创建,当然也可以调用:

1

Executors.unconfigurableScheduledExecutorService

   方法来创建一个DelegatedScheduledExecutorService其实这个类就是包装了下下scheduleExecutor,也就是这只是一个壳,英文理解就是被委派的意思,被托管的意思。

 

终于完了,但是有些地方还不是很理解,明天继续吧,感谢博主的分享,还有多线程核心编码作者;

哦、原来也是转载的,不过谢谢分享,文章主体的原文链接: http://blog.csdn.net/xieyuooo/article/details/8607220

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值