timertask_timertask导致内存溢出 - CSDN
精华内容
参与话题
  • Timer和TimerTask详解

    2019-05-15 15:44:38
    Timer和TimerTask  Timer是jdk中提供的一个定时器工具,使用的时候会在主线程之外起一个单独的线程执行指定的计划任务,可以指定执行一次或者反复执行多次。  TimerTask是一个实现了Runnable接口的抽象类,代表...

    Timer和TimerTask

      Timer是jdk中提供的一个定时器工具,使用的时候会在主线程之外起一个单独的线程执行指定的计划任务,可以指定执行一次或者反复执行多次。

      TimerTask是一个实现了Runnable接口的抽象类,代表一个可以被Timer执行的任务。

     

    import android.app.Activity;
    import android.os.Bundle;
    import android.util.Log;
    
    import java.util.Timer;
    import java.util.TimerTask;
    
    public class TextActivity extends Activity {
        private Timer mTimer = new Timer();
        private TimerTask mTimerTask;
        private int SECOND = 1;
    
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.test1);
            mTimerTask = new TimerTask( ) {
                @Override
                public void run() {
                    //sync(); //要做的事情的一个方法
                    Log.e("TAG","wang");
                }
            };
            //参数分别为:TimerTask对象  距离执行方法的时间  重复执行时间
            mTimer.schedule( mTimerTask, 5000, SECOND * 1000 );
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            mTimer.cancel();
        }
    }

    实现效果图:

    展开全文
  • timerTask实现每天定时一段时间内执行定时任务,这个是自己写的代码,有些和我的项目有关,可以删掉,但是其主要的都在里面,我写了注释。
  • java定时器-Timer和TimerTask详解

    万次阅读 2018-07-17 20:56:21
    1、例子入手 package pers.growing.test; import java.util.Timer;...import java.util.TimerTask; public class Main { /** * 延迟100ms后,间隔1s打印出:hello world * * @param args * @throws Inte...

    1、例子入手

    package pers.growing.test;
    
    import java.util.Timer;
    import java.util.TimerTask;
    
    public class Main {
    
        /**
         * 延迟100ms后,间隔1s打印出:hello world
         *
         * @param args
         * @throws InterruptedException
         */
        public static void main(String[] args) throws InterruptedException {
            Timer t = new Timer();
            t.scheduleAtFixedRate(new TimerTask() {
                @Override
                public void run() {
                    System.out.println("hello world");
                }
            }, 100, 1000);
    
        }
    
    }

    结果:

    hello world
    hello world
    hello world
    hello world
    hello world
    hello world
    hello world

    2、类讲解

    TimerTask.java:主要为任务的具体内容。

    Timer.java中含有3个类:Timer、TimerThread、TaskQueue。

    三者关系为:TaskQueue中存放一些列将要执行的TimerTask,以数组的形式存放,下标约小(注:下标为0不处理,即使用的最小下标为1),则表明优先级越高。

    TimerThread为Thread的扩展类,会一直从TaskQueue中获取下标为1的TimerTask进行执行。并根据该TimerTask是否需要重复执行来决定是否放回到TaskQueue中。

    Timer用于配置用户期望的任务执行时间、执行次数、执行内容。它内部会配置TimerThread、TaskQueue。

    3、源码解读

    Timer类下的方法如下:

    Timer中涉及到4个成员变量,queue、thread已经在上面介绍过了,对于nextSerialNumber,只是用于命名默认的thread名称使用。threadReaper为了在GC时进行相关处理,后面再介绍。

    Timer的构造函数实现大同小异,以Timer(String,boolean)为例:

      public Timer(String name, boolean isDaemon) {
            thread.setName(name); //设置成员变量的线程名称
            thread.setDaemon(isDaemon); //该线程是否为守护线程
            thread.start();//起线程
        }

    schedule()以schedule(TimerTask,long,long)为例:

     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); //最后调用了sched方法
        }

    这一块有困惑的可能是为什么period取了负数?而且所有的schedule(...)方法,最后传给sched中的period都是负的。之后再介绍。

    scheduleAtFixedRate()以scheduleAtFixedRate(TimerTask,long,long)为例:

        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); //也是调用sched方法
        }

    此时会发现schedule与scheduleAtFixedRate似乎区别不大,唯一有区别的是schedule最后传值给sched的period是负数,而scheduleAtFixedRate传的是正数。

    在schedule方法的注释上,有这段内容:

         * <p>In fixed-delay execution, each execution is scheduled relative to
         * the actual execution time of the previous execution.  If an execution
         * is delayed for any reason (such as garbage collection or other
         * background activity), subsequent executions will be delayed as well.
         * In the long run, the frequency of execution will generally be slightly
         * lower than the reciprocal of the specified period (assuming the system
         * clock underlying <tt>Object.wait(long)</tt> is accurate).

    翻译:如果出现某一次任务的延迟,那么之后的任务都会以period为周期进行延迟。

    而scheduleAtFixedRate方法也有对应注释:

         * <p>In fixed-rate execution, each execution is scheduled relative to the
         * scheduled execution time of the initial execution.  If an execution is
         * delayed for any reason (such as garbage collection or other background
         * activity), two or more executions will occur in rapid succession to
         * "catch up."  In the long run, the frequency of execution will be
         * exactly the reciprocal of the specified period (assuming the system
         * clock underlying <tt>Object.wait(long)</tt> is accurate).

    翻译:每次的执行都是以初始时计算好的时间为准,如果出现某次任务的延迟,则之后的任务会快速执行,即按计划时间执行。

    那么看下Sched()方法实现:

     private void sched(TimerTask task, long time, long period) {//接收具体任务,第一次执行时间,周期
            if (time < 0)
                throw new IllegalArgumentException("Illegal execution time.");
    
            // Constrain value of period sufficiently to prevent numeric
            // overflow while still being effectively infinitely large.
            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; //给TimerTask赋值
                    task.period = period;
                    task.state = TimerTask.SCHEDULED;
                }
    
                queue.add(task);//将TimerTask放到队列中,并进行队列排序
                if (queue.getMin() == task)//如果队列里恰好下标为1的任务为当前的task,则直接唤醒
                    queue.notify();
            }
        }

    queue中的add和getMin操作如下:

        void add(TimerTask task) {
            // Grow backing store if necessary
            if (size + 1 == queue.length)
                queue = Arrays.copyOf(queue, 2*queue.length);
    
            queue[++size] = task;
            fixUp(size);//让task进行排序,排序并不是十分严谨,将nextExecutionTime较小的往前排
        }
    
        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;
            }
        }
    
       
        TimerTask getMin() { //注意最小的值是从下标为1的获取,queue[0]其实没用到
            return queue[1];
        }

    数据已经放到queue中了,那么看下是什么时候执行的。在之前Timer的构造函数这块,有一句是:thread.start();说明TimerThread在Timer初始化之后就一直启用着,那看下它的处理。

     public void run() {
            try {
                mainLoop(); //主要实现内容
            } finally {
                // Someone killed this Thread, behave as if Timer cancelled
                synchronized(queue) {
                    newTasksMayBeScheduled = false;
                    queue.clear();  // Eliminate obsolete references
                }
            }
        }
    
        /**
         * The main timer loop.  (See class comment.)
         */
        private void mainLoop() {
            while (true) {
                try {
                    TimerTask task;
                    boolean taskFired;
                    synchronized(queue) {
                        // Wait for queue to become non-empty
                       //如果队列为空并且是有标志位,则等待。没有标志位的情况为不在需要执行timer了,比如cancel或被gc的时候
                        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
                        long currentTime, executionTime;//分别是当前时间、理论执行时间
                        task = queue.getMin();//获取就近的task
                        synchronized(task.lock) {
                           //如果该task已经被置为cancelled,则将它从队列里面移出
                            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) { // period表示该task是一次性的,用完就移出
                                    queue.removeMin();//移出task,这块会有queue的重新排序
                                    task.state = TimerTask.EXECUTED;//更新状态为执行中
                                } else {
                            //可重复执行的task操作,将重新计算下次执行时间,并重新排序    
                    //重点,此处解释为什么period分正负:区别schedule方法和scheduleAtFixedRate
                            //如果是负数,则以当前时间为准,往后计算下次执行时间
                            //如果是正数,则以理论时间为准,往后计算下次执行时间
                                    queue.rescheduleMin(
                                      task.period<0 ? currentTime   - task.period
                                                    : executionTime + task.period);
                                }
                            }
                        }
                        if (!taskFired) // 如果还没到任务执行时间就处于等待
                            queue.wait(executionTime - currentTime);
                    }
                    if (taskFired)  // 到执行时间了
                    //执行task中的run方法,而不是start方法,所以并不是另起一个线程进行操作
                        task.run();
                } catch(InterruptedException e) {//如果是不能捕获的异常,就会有风险了
                }
            }
        }

    关于queue中的removeMin()和rescheduleMin()方法如下:

        void removeMin() {//前两行赋值可能会将queue乱序,所以才会有fixDown重新排序
            queue[1] = queue[size];
            queue[size--] = null;  // Drop extra reference to prevent memory leak
            fixDown(1);
        }
    
        //因为取的时候也是取下标为1的task进行操作,所以此次也是将下标为1的task重新赋时间,并排序
        void rescheduleMin(long newTime) {
            queue[1].nextExecutionTime = newTime;
            fixDown(1);
        }
    
        //和fixUp方法类似,该排序单独看也并非严谨,但由于每次操作都会经历,所以应该是准的吧!
        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++; // j indexes smallest kid
                if (queue[k].nextExecutionTime <= queue[j].nextExecutionTime)
                    break;
                TimerTask tmp = queue[j];  queue[j] = queue[k]; queue[k] = tmp;
                k = j;
            }
        }

    当timerTask调用了timerTask.cancel()操作时,也可以人为的将它从Timer队列中清除掉,方法如下:

     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); //由于i取值时必然大于0,所以肯定能够找到正常的数据
                         result++;
                     }
                 }
    
                 if (result != 0)
                     queue.heapify();//重新排序
             }
    
             return result;
         }

    queue中的quickRemove和heapify方法:

        void quickRemove(int i) { //只要简单赋值就行了,最后排序交给heapify()
            assert i <= size;
    
            queue[i] = queue[size];
            queue[size--] = null;  // Drop extra ref to prevent memory leak
        }
    
        void heapify() {
            for (int i = size/2; i >= 1; i--)
                fixDown(i); //在前面的篇幅中介绍过了
        }

    当然如果Timer也可以人为的取消,不在继续定时任务。其方法如下:

       public void cancel() {
            synchronized(queue) {
                thread.newTasksMayBeScheduled = false;
                queue.clear(); //会将队列中的所有的task赋值为null
                queue.notify();  // 通知thread中的mainLoop进行break操作
            }
        }

    综上,其实大部分流程就屡清楚了。顺带介绍下Timer中的threadReaper。代码如下:

       /**
         * This object causes the timer's task execution thread to exit
         * gracefully when there are no live references to the Timer object and no
         * tasks in the timer queue.  It is used in preference to a finalizer on
         * Timer as such a finalizer would be susceptible to a subclass's
         * finalizer forgetting to call it.
         */
        private final Object threadReaper = new Object() {
            protected void finalize() throws Throwable {
                synchronized(queue) {
                    thread.newTasksMayBeScheduled = false;
                    queue.notify(); // In case queue is empty.
                }
            }
        };

    重写了finalize方法,该方法为GC时候调用,主要使Timer中的thread能够优雅退出。

    4、总结

    优势:可以实现比较轻量级的定时任务,而且很方便

    劣势:

    ①由于Timer只是创建了一个thread去执行queue中的task,那么就可能会出现上一个任务执行延迟了,会影响到下一个定时任务。

    ②在TimerThread#mainloop中,也可看到,如果抛出了InterruptedException之外无法捕获到的异常时,mainloop就会中断,Timer也就无法使用了。

     

              

    展开全文
  • Java Timer TimerTask示例

    万次阅读 2018-11-14 11:09:18
    Java java.util.Timer是一个实用程序类,可用于...java.util.TimerTask是一个实现Runnable接口的抽象类,我们需要扩展这个类来创建我们自己的TimerTask,它可以使用java Timer类进行调度。 Java计时器示例 J...

    Java java.util.Timer是一个实用程序类,可用于调度将来某个时间执行的线程。Java Timer类可用于计划要一次运行的任务或定期运行的任务。

    Java TimerTask

    java.util.TimerTask是一个实现Runnable接口的抽象类,我们需要扩展这个类来创建我们自己的TimerTask,它可以使用java Timer类进行调度。

    Java计时器示例

    java计时器示例,java计时器,java timertask,java timertask示例

    Java Timer类是线程安全的,多个线程可以共享一个Timer对象,而无需外部同步。Timer类使用java.util.TaskQueue以给定的定期间隔添加任务,并且在任何时候只能有一个线程运行TimerTask,例如,如果要创建一个每10秒运行一次的Timer,但单线程执行需要20秒,然后Timer对象将继续向队列添加任务,一旦一个线程完成,它将通知队列,另一个线程将开始执行。

    Java Timer类使用Object wait和notify方法来安排任务。

    这是一个简单的Java Timer和TimerTask示例程序。

    
    package com.journaldev.threads;
    
    import java.util.Date;
    import java.util.Timer;
    import java.util.TimerTask;
    
    public class MyTimerTask extends TimerTask {
    
        @Override
        public void run() {
            System.out.println("Timer task started at:"+new Date());
            completeTask();
            System.out.println("Timer task finished at:"+new Date());
        }
    
        private void completeTask() {
            try {
                //assuming it takes 20 secs to complete the task
                Thread.sleep(20000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        public static void main(String args[]){
            TimerTask timerTask = new MyTimerTask();
            //running timer task as daemon thread
            Timer timer = new Timer(true);
            timer.scheduleAtFixedRate(timerTask, 0, 10*1000);
            System.out.println("TimerTask started");
            //cancel after sometime
            try {
                Thread.sleep(120000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            timer.cancel();
            System.out.println("TimerTask cancelled");
            try {
                Thread.sleep(30000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
    }
    

    请注意,一个线程执行将花费20秒,但Java Timer对象被安排为每10秒运行一次任务。这是程序的输出:

    
    TimerTask started
    Timer task started at:Wed Dec 26 19:16:39 PST 2012
    Timer task finished at:Wed Dec 26 19:16:59 PST 2012
    Timer task started at:Wed Dec 26 19:16:59 PST 2012
    Timer task finished at:Wed Dec 26 19:17:19 PST 2012
    Timer task started at:Wed Dec 26 19:17:19 PST 2012
    Timer task finished at:Wed Dec 26 19:17:39 PST 2012
    Timer task started at:Wed Dec 26 19:17:39 PST 2012
    Timer task finished at:Wed Dec 26 19:17:59 PST 2012
    Timer task started at:Wed Dec 26 19:17:59 PST 2012
    Timer task finished at:Wed Dec 26 19:18:19 PST 2012
    Timer task started at:Wed Dec 26 19:18:19 PST 2012
    TimerTask cancelled
    Timer task finished at:Wed Dec 26 19:18:39 PST 2012
    

    输出确认如果任务已在执行,Timer将等待它完成,一旦完成,它将再次从队列中开始下一个任务。

    可以创建Java Timer对象以将相关任务作为守护程序线程运行。Timer cancel()方法用于终止计时器并丢弃任何计划任务,但它不会干扰当前正在执行的任务并让它完成。如果计时器作为守护程序线程运行,无论我们是否取消它,它将在所有用户线程完成执行后立即终止。

    Timer类包含几个schedule()方法,用于安排任务在给定日期或延迟一段时间后运行一次。有几个scheduleAtFixedRate()方法以一定的间隔定期运行任务。

    在使用Timer调度任务时,您应该确保时间间隔超过正常的线程执行,否则任务队列大小将继续增长,最终任务将始终执行。这就是Java Timer和Java TimerTask的快速综述。

    展开全文
  • Timer与TimerTask的真正原理&使用介绍

    万次阅读 多人点赞 2013-02-28 12:24:16
    其实就Timer来讲就是一个调度器,而TimerTask呢只是一个实现了run方法的一个类,而具体的TimerTask需要由你自己来实现,例如这样: Timer timer = new Timer(); timer.schedule(new TimerTask() { public void run...

    其实就Timer来讲就是一个调度器,而TimerTask呢只是一个实现了run方法的一个类,而具体的TimerTask需要由你自己来实现,例如这样:

    Timer timer = new Timer();
    timer.schedule(new TimerTask() {
    		public void run() {
    			System.out.println("abc");
    		}
    }, 200000 , 1000);
    

    这里直接实现一个TimerTask(当然,你可以实现多个TimerTask,多个TimerTask可以被一个Timer会被分配到多个Timer中被调度,后面会说到Timer的实现机制就是说内部的调度机制),然后编写run方法,20s后开始执行,每秒执行一次,当然你通过一个timer对象来操作多个timerTask,其实timerTask本身没什么意义,只是和timer集合操作的一个对象,实现它就必然有对应的run方法,以被调用,他甚至于根本不需要实现Runnable,因为这样往往混淆视听了,为什么呢?也是本文要说的重点。

     

    在说到timer的原理时,我们先看看Timer里面的一些常见方法:

    public void schedule(TimerTask task, long delay)

    这个方法是调度一个task,经过delay(ms)后开始进行调度,仅仅调度一次。

    public void schedule(TimerTask task, Date time)

    在指定的时间点time上调度一次。

    public void schedule(TimerTask task, long delay, long period)

    这个方法是调度一个task,在delay(ms)后开始调度,每次调度完后,最少等待period(ms)后才开始调度。

    public void schedule(TimerTask task, Date firstTime, long period)

    和上一个方法类似,唯一的区别就是传入的第二个参数为第一次调度的时间。

    public void scheduleAtFixedRate(TimerTask task, long delay, long period)

    调度一个task,在delay(ms)后开始调度,然后每经过period(ms)再次调度,貌似和方法:schedule是一样的,其实不然,后面你会根据源码看到,schedule在计算下一次执行的时间的时候,是通过当前时间(在任务执行前得到) + 时间片,而scheduleAtFixedRate方法是通过当前需要执行的时间(也就是计算出现在应该执行的时间)+ 时间片,前者是运行的实际时间,而后者是理论时间点,例如:schedule时间片是5s,那么理论上会在5、10、15、20这些时间片被调度,但是如果由于某些CPU征用导致未被调度,假如等到第8s才被第一次调度,那么schedule方法计算出来的下一次时间应该是第13s而不是第10s,这样有可能下次就越到20s后而被少调度一次或多次,而scheduleAtFixedRate方法就是每次理论计算出下一次需要调度的时间用以排序,若第8s被调度,那么计算出应该是第10s,所以它距离当前时间是2s,那么再调度队列排序中,会被优先调度,那么就尽量减少漏掉调度的情况。

    public void scheduleAtFixedRate(TimerTask task, Date firstTime,long period)

    方法同上,唯一的区别就是第一次调度时间设置为一个Date时间,而不是当前时间的一个时间片,我们在源码中会详细说明这些内容。

    接下来看源码

    首先看Timer的构造方法有几种:

     

    构造方法1:无参构造方法,简单通过Tiemer为前缀构造一个线程名称:

    public Timer() {
        this("Timer-" + serialNumber());
    }
    

    传入是否为后台线程,如果设置为后台线程,则主线程结束后,timer自动结束,而无需使用cancel来完成对timer的结束

    构造方法2:传入了是否为后台线程,后台线程当且仅当进程结束时,自动注销掉。

    public Timer(boolean isDaemon) {
        this("Timer-" + serialNumber(), isDaemon);
    }
    

    另外两个构造方法负责传入名称和将timer启动:

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

    这里有一个thread,这个thread很明显是一个线程,被包装在了Timer类中,我们看下这个thread的定义是:

    private TimerThread thread = new TimerThread(queue);

    而定义TimerThread部分的是:

    class TimerThread extends Thread {

    看到这里知道了,Timer内部包装了一个线程,用来做独立于外部线程的调度,而TimerThread是一个default类型的,默认情况下是引用不到的,是被Timer自己所使用的。

     

    接下来看下有那些属性

    除了上面提到的thread,还有一个很重要的属性是:

    private TaskQueue queue = new TaskQueue();

    看名字就知道是一个队列,队列里面可以先猜猜看是什么,那么大概应该是我要调度的任务吧,先记录下了,接下来继续向下看:

    里面还有一个属性是:threadReaper,它是Object类型,只是重写了finalize方法而已,是为了垃圾回收的时候,将相应的信息回收掉,做GC的回补,也就是当timer线程由于某种原因死掉了,而未被cancel,里面的队列中的信息需要清空掉,不过我们通常是不会考虑这个方法的,所以知道java写这个方法是干什么的就行了。

    接下来看调度方法的实现:

    对于上面6个调度方法,我们不做一一列举,为什么等下你就知道了:

    来看下方法:

    public void schedule(TimerTask task, long delay)

    的源码如下:

        public void schedule(TimerTask task, long delay) {
            if (delay < 0)
                throw new IllegalArgumentException("Negative delay.");
            sched(task, System.currentTimeMillis()+delay, 0);
        }
    

    这里调用了另一个方法,将task传入,第一个参数传入System.currentTimeMillis()+delay可见为第一次需要执行的时间的时间点了(如果传入Date,就是对象.getTime()即可,所以传入Date的几个方法就不用多说了),而第三个参数传入了0,这里可以猜下要么是时间片,要么是次数啥的,不过等会就知道是什么了;另外关于方法:sched的内容我们不着急去看他,先看下重载的方法中是如何做的

    在看看方法:

    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);
        }
    

    看来也调用了方法sched来完成调度,和上面的方法唯一的调度时候的区别是增加了传入的period,而第一个传入的是0,所以确定这个参数为时间片,而不是次数,注意这个里的period加了一个负数,也就是取反,也就是我们开始传入1000,在调用sched的时候会变成-1000,其实最终阅读完源码后你会发现这个算是老外对于一种数字的理解,而并非有什么特殊的意义,所以阅读源码的时候也有这些困难所在。

    最后再看个方法是:

    public void scheduleAtFixedRate(TimerTasktask,long delay,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);
        }
    

    唯一的区别就是在period没有取反,其实你最终阅读完源码,上面的取反没有什么特殊的意义,老外不想增加一个参数来表示scheduleAtFixedRate,而scheduleAtFixedRate和schedule的大部分逻辑代码一致,因此用了参数的范围来作为区分方法,也就是当你传入的参数不是正数的时候,你调用schedule方法正好是得到scheduleAtFixedRate的功能,而调用scheduleAtFixedRate方法的时候得到的正好是schedule方法的功能,呵呵,这些讨论没什么意义,讨论实质和重点:

     

    来看sched方法的实现体:

         private void sched(TimerTask task, long time, long period) {
            if (time < 0)
                throw new IllegalArgumentException("Illegal execution time.");
    
            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();
            }
        }
    

    queue为一个队列,我们先不看他数据结构,看到他在做这个操作的时候,发生了同步,所以在timer级别,这个是线程安全的,最后将task相关的参数赋值,主要包含nextExecutionTime(下一次执行时间),period(时间片),state(状态),然后将它放入queue队列中,做一次notify操作,为什么要做notify操作呢?看了后面的代码你就知道了。

     

    简言之,这里就是讲task放入队列queue的过程,此时,你可能对queue的结构有些兴趣,那么我们先来看看queue属性的结构TaskQueue:

    class TaskQueue {
    
        private TimerTask[] queue = new TimerTask[128];
    
        private int size = 0;
    

     

    可见,TaskQueue的结构很简单,为一个数组,加一个size,有点像ArrayList,是不是长度就128呢,当然不是,ArrayList可以扩容,它可以,只是会造成内存拷贝而已,所以一个Timer来讲,只要内部的task个数不超过128是不会造成扩容的;内部提供了add(TimerTask)、size()、getMin()、get(int)、removeMin()、quickRemove(int)、rescheduleMin(long newTime)、isEmpty()、clear()、fixUp()、fixDown()、heapify();

    这里面的方法大概意思是:

    add(TimerTaskt)为增加一个任务

    size()任务队列的长度

    getMin()获取当前排序后最近需要执行的一个任务,下标为1,队列头部0是不做任何操作的。

    get(inti)获取指定下标的数据,当然包括下标0.

    removeMin()为删除当前最近执行的任务,也就是第一个元素,通常只调度一次的任务,在执行完后,调用此方法,就可以将TimerTask从队列中移除。

    quickRmove(inti)删除指定的元素,一般来说是不会调用这个方法的,这个方法只有在Timer发生purge的时候,并且当对应的TimerTask调用了cancel方法的时候,才会被调用这个方法,也就是取消某个TimerTask,然后就会从队列中移除(注意如果任务在执行中是,还是仍然在执行中的,虽然在队列中被移除了),还有就是这个cancel方法并不是Timer的cancel方法而是TimerTask,一个是调度器的,一个是单个任务的,最后注意,这个quickRmove完成后,是将队列最后一个元素补充到这个位置,所以此时会造成顺序不一致的问题,后面会有方法进行回补。

    rescheduleMin(long newTime)是重新设置当前执行的任务的下一次执行时间,并在队列中将其从新排序到合适的位置,而调用的是后面说的fixDown方法。

    对于fixUpfixDown方法来讲,前者是当新增一个task的时候,首先将元素放在队列的尾部,然后向前找是否有比自己还要晚执行的任务,如果有,就将两个任务的顺序进行交换一下。而fixDown正好相反,执行完第一个任务后,需要加上一个时间片得到下一次执行时间,从而需要将其顺序与后面的任务进行对比下。

    其次可以看下fixDown的细节为:

        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++; // j indexes smallest kid
                if (queue[k].nextExecutionTime <= queue[j].nextExecutionTime)
                    break;
                TimerTask tmp = queue[j];  queue[j] = queue[k]; queue[k] = tmp;
                k = j;
            }
        }
    

    这种方式并非排序,而是找到一个合适的位置来交换,因为并不是通过队列逐个找的,而是每次移动一个二进制为,例如传入1的时候,接下来就是2、4、8、16这些位置,找到合适的位置放下即可,顺序未必是完全有序的,它只需要看到距离调度部分的越近的是有序性越强的时候就可以了,这样即可以保证一定的顺序性,达到较好的性能。

     

    最后一个方法是heapify,其实就是将队列的后半截,全部做一次fixeDown的操作,这个操作主要是为了回补quickRemove方法,当大量的quickRmove后,顺序被打乱后,此时将一半的区域做一次非常简单的排序即可。

     

    这些方法我们不在说源码了,只需要知道它提供了类似于ArrayList的东西来管理,内部有很多排序之类的处理,我们继续回到Timer,里面还有两个方法是:cancel()和方法purge()方法,其实就cancel方法来讲,一个取消操作,在测试中你会发现,如果一旦执行了这个方法timer就会结束掉,看下源码是什么呢:

        public void cancel() {
            synchronized(queue) {
                thread.newTasksMayBeScheduled = false;
                queue.clear();
                queue.notify();  // In case queue was already empty.
            }
        }
    

    貌似仅仅将队列清空掉,然后设置了newTasksMayBeScheduled状态为false,最后让队列也调用了下notify操作,但是没有任何地方让线程结束掉,那么就要回到我们开始说的Timer中包含的thread为:TimerThread类了,在看这个类之前,再看下Timer中最后一个purge()类,当你对很多Task做了cancel操作后,此时通过调用purge方法实现对这些cancel掉的类空间的回收,上面已经提到,此时会造成顺序混乱,所以需要调用队里的heapify方法来完成顺序的重排,源码如下:


        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,也就是我们开始所提及的那个参数在cancel的时候会被设置为false。

    另一个属性定义了

        private TaskQueue queue;

    也就是我们所调用的queue了,这下联通了吧,不过这里是queue是通过构造方法传入的,传入后赋值用以操作,很明显是Timer传递给这个线程的,我们知道它是一个线程,所以执行的中心自然是run方法了,所以看下run方法的body部分是:

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

    try很简单,就一个mainLoop,看名字知道是主循环程序,finally中也就是必然执行的程序为将参数为为false,并将队列清空掉。

    那么最核心的就是mainLoop了,是的,看懂了mainLoop一切都懂了:

        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
                        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
                                    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) {
                }
            }
        }
    

    可以发现这个timer是一个死循环程序,除非遇到不能捕获的异常或break才会跳出,首先注意这段代码:

    while (queue.isEmpty() &&newTasksMayBeScheduled)

                            queue.wait();

    循环体为循环过程中,条件为queue为空且newTasksMayBeScheduled状态为true,可以看到这个状态其关键作用,也就是跳出循环的条件就是要么队列不为空,要么是newTasksMayBeScheduled状态设置为false才会跳出,而wait就是在等待其他地方对queue发生notify操作,从上面的代码中可以发现,当发生add、cancel以及在threadReaper调用finalize方法的时候会被调用,第三个我们基本可以不考虑其实发生add的时候也就是当队列还是空的时候,发生add使得队列不为空就跳出循环,而cancel是设置了状态,否则不会进入这个循环,那么看下面的代码:

    if (queue.isEmpty())

             break;

    当跳出上面的循环后,如果是设置了newTasksMayBeScheduled状态为false跳出,也就是调用了cancel,那么queue就是空的,此时就直接跳出外部的死循环,所以cancel就是这样实现的,如果下面的任务还在跑还没运行到这里来,cancel是不起作用的。

     

    接下来是获取一个当前系统时间和上次预计的执行时间,如果预计执行的时间小于当前系统时间,那么就需要执行,此时判定时间片是否为0,如果为0,则调用removeMin方法将其移除,否则将task通过rescheduleMin设置最新时间并排序:

    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
               queue.rescheduleMin(
               task.period<0 ? currentTime   - task.period
                                  : executionTime + task.period);
         }
    }
    

    这里可以看到,period为负数的时候,就会被认为是按照按照当前系统时间+一个时间片来计算下一次时间,就是前面说的schedule和scheduleAtFixedRate的区别了,其实内部是通过正负数来判定的,也许java是不想增加参数,而又想增加程序的可读性,才这样做,其实通过正负判定是有些诡异的,也就是你如果在schedule方法传入负数达到的功能和scheduleAtFixedRate的功能是一样的,相反在scheduleAtFixedRate方法中传入负数功能和schedule方法是一样的。

    同时你可以看到period为0,就是只执行一次,所以时间片正负0都用上了,呵呵,然后再看看mainLoop接下来的部分:

    if (!taskFired)// Taskhasn't yet fired; wait

        queue.wait(executionTime- currentTime);

    这里是如果任务执行时间还未到,就等待一段时间,当然这个等待很可能会被其他的线程操作add和cancel的时候被唤醒,因为内部有notify方法,所以这个时间并不是完全准确,在这里大多数情况下是考虑Timer内部的task信息是稳定的,cancel方法唤醒的话是另一回事。

     

    最后:

    if (taskFired) // Task fired; run it, holding no locks

    task.run();

    如果线程需要执行,那么调用它的run方法,而并非启动一个新的线程或从线程池中获取一个线程来执行,所以TimerTask的run方法并不是多线程的run方法,虽然实现了Runnable,但是仅仅是为了表示它是可执行的,并不代表它必须通过线程的方式来执行的。

     

    回过头来再看看:

    TimerTimerTask的简单组合是多线程的嘛?不是,一个Timer内部包装了“一个Thread”和“一个Task”队列,这个队列按照一定的方式将任务排队处理,包含的线程在Timer的构造方法调用时被启动,这个Thread的run方法无限循环这个Task队列,若队列为空且没发生cancel操作,此时会一直等待,如果等待完成后,队列还是为空,则认为发生了cancel从而跳出死循环,结束任务;循环中如果发现任务需要执行的时间小于系统时间,则需要执行,那么根据任务的时间片从新计算下次执行时间,若时间片为0代表只执行一次,则直接移除队列即可。

    但是是否能实现多线程呢?可以,任何东西是否是多线程完全看个人意愿,多个Timer自然就是多线程的,每个Timer都有自己的线程处理逻辑,当然Timer从这里来看并不是很适合很多任务在短时间内的快速调度,至少不是很适合同一个timer上挂很多任务,在多线程的领域中我们更多是使用多线程中的:

    Executors.newScheduledThreadPool

    来完成对调度队列中的线程池的处理,内部通过new ScheduledThreadPoolExecutor来创建线程池的Executor的创建,当然也可以调用:

    Executors.unconfigurableScheduledExecutorService

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

    展开全文
  • 在使用Timer来做定时任务时主要用到的两个类分别是Timer和TimerTask。 一、定时任务之Timer: 1.首先我们新建一个任务类(eg:MyTimerTssk)继承TimerTask类,从TimerTask(public abstract class TimerTask implements...
  • Timer和TimerTask定时器使用

    万次阅读 2018-02-13 08:59:35
    TimerTask一个抽象类,它的子类代表一个可以被Timer计划的任务。具体的任务在TimerTask中run接口中实现。 通过Timer中的schedule方法启动定时任务。 一、运行定时器 启动一个定时器实质是启动一个线程 1、在...
  • TimerTask的详细使用方法

    千次阅读 2012-07-17 13:33:40
    如果要执行一些简单的定时器任务,无须做复杂的控制,也无须保存状态,那么可以考虑使用... java.util.TimerTask;   要运行一个定时任务,最基本的步骤如下: 1、建立一个要执行的任务TimerTask。 2、创建一个Tim
  • TimerTask

    2018-05-25 10:00:00
    详解java定时任务在我们编程过程中如果需要执行...一、简介在java中一个完整定时任务需要由Timer、TimerTask两个类来配合完成。 API中是这样定义他们的,Timer:一种工具,线程用其安排以后在后台线程中执行的任务。...
  • Java线程(五):Timer和TimerTask

    万次阅读 多人点赞 2019-04-29 21:08:13
    Timer和TimerTask可以做为实现线程的第三种方式,前两中方式分别是继承自Thread类和实现Runnable接口。 Timer是一种线程设施,用于安排以后在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行,可以...
  • android TimerTask 的简单应用Java:如何正确使用Timer【java定时器的使用(Timer) 】android应用开发中常常会用到定时器,不可避免的需要用到 TimerTask 定时器任务这个类 下面简单的一个示例演示了如何使用...
  • 在JDK 5.0之前,java.util.Timer/TimerTask是唯一的内置任务调度方法,而且在很长一段时间里很热衷于使用这种方式进行周期性任务调度。 首先研究下Timer/TimerTask的特性(至于javax.swing.Timer就不再研究了)。 ...
  • 下面内容转载自: ... 其实就Timer来讲就是一个调度器,而TimerTask呢只是一个实现了run方法的一个类,而具体的TimerTask需要由你自己来实现,例如这样: 1 2 3 4 5 6 T
  • TimerTask 和 Quartz比较

    千次阅读 2017-06-09 17:17:08
    Quartz拥有TimerTask所有的功能,而TimerTask则没有。   任务类的数量  TimerTask和Quartz每次执行任务时,每次调用的是不是都是同一个任务类对象,还是每次都不一样?现在做如下实验,每次执行任务时,...
  • Java定时器(一)Timer和TimerTask

    千次阅读 2018-04-12 10:33:14
    方式一:设定指定任务task在指定时间time执行 schedule(TimerTask task, Date date) public static void main(String[] args) throws Exception { // TODO Auto-generated method stub new Timer()....
  • Timer和TimerTask

    2019-04-29 13:56:42
    package 线程; import java.util.Date; import java.util.Timer;...import java.util.TimerTask; public class ThreadRunTest implements Runnable{ @Override public void run() { System.out.prin...
  • 一、Handler的定义:  主要接受子线程发送的数据, 并用此数据配合主线程更新UI.  解释: 当应用程序启动时,Android首先会开启一个主线程 (也就是UI线程) , 主线程为管理界面中的UI控件,进行事件分发, 比如说...
  • 在我们编程过程中如果需要执行一些简单的定时任务,...在java中一个完整定时任务需要由Timer、TimerTask两个类来配合完成。 API中是这样定义他们的,Timer:一种工具,线程用其安排以后在后台线程中执行的任务。可安
1 2 3 4 5 ... 20
收藏数 35,653
精华内容 14,261
关键字:

timertask