Home > Java > javaTutorial > body text

java-concurrency-Timer and TimerTask

黄舟
Release: 2017-01-19 11:43:31
Original
1226 people have browsed it

Timer is a scheduler, and TimerTask is just a class that implements the run method, and the specific TimerTask needs to be implemented by yourself

[code]Timer timer = new Timer();
timer.schedule(new TimerTask() {
        public void run() {
            System.out.println("abc");
        }
}, 200000 , 1000);
Copy after login

public void schedule(TimerTask task, long delay )

This method is to schedule a task, and start scheduling after delay (ms). It is only scheduled once.

public void schedule(TimerTask task, Date time)

Schedule once at the specified time point time.

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

This method is to schedule a task. It starts scheduling after delay (ms). After each scheduling, it waits for at least period. (ms) before scheduling starts.

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

Similar to the previous method, the only difference is that the second parameter passed in is the time of the first schedule.

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

A task is scheduled after delay (ms), and then scheduled again after period (ms), which looks like the same Method: The schedule is the same. In fact, it is not the case. You will see from the source code later that when the schedule calculates the next execution time, it uses the current time (obtained before the task is executed) + time slice, while the scheduleAtFixedRate method uses the current time. The time required to be executed (that is, the calculated time that should be executed now) + time slice, the former is the actual running time, and the latter is the theoretical time point. For example: the schedule time slice is 5s, then theoretically it will be at 5, 10, These time slices 15 and 20 are scheduled, but if they are not scheduled due to some CPU requisition, if they are not scheduled for the first time until the 8s, then the next time calculated by the schedule method should be the 13th s instead of the 10s. In this way, it is possible that the next time it will be 20 seconds later, it will be scheduled one or more times. The scheduleAtFixedRate method theoretically calculates the next time that needs to be scheduled for sorting. If the 8th second is scheduled, then the calculated time should be 10s, so it is 2s away from the current time, then it will be scheduled first in the rescheduling queue sorting, so try to minimize the situation of missing scheduling.

[code]public Timer() {
    this("Timer-" + serialNumber());
}
Copy after login

The created thread is not the main thread. After the main thread ends, the timer automatically ends without using cancel to complete the end of the timer.

[code]public Timer(boolean isDaemon) {
    this("Timer-" + serialNumber(), isDaemon);
}
Copy after login

Inputs whether it is a background thread. The background thread will automatically log out when and only when the process ends.

[code]public Timer(String name, boolean isDaemon) {
      thread.setName(name);
      thread.setDaemon(isDaemon);
      thread.start();
  }
Copy after login

There is a thread here. This thread is obviously a thread and is wrapped in the Timer class. Let’s take a look at the definition of this thread. Timer wraps a thread internally and is used to do things independent of external threads. Scheduling, and TimerThread is a default type, which cannot be referenced by default and is used by Timer itself.

private TaskQueue queue = new TaskQueue();
Copy after login

threadReaper, it is an Object type, it just overrides the finalize method. It is used to recycle the corresponding information during garbage collection and do GC back-up, that is, when the timer thread is due to some kind of The reason is that it died without being canceled, and the information in the queue needs to be cleared. However, we usually do not consider this method, so we only need to know what Java writes this method for.

Scheduling method

public void schedule(TimerTask task, long delay)

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

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

[code]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);
    }
Copy after login

The method sched is also called to complete the scheduling. The only difference from the above method in scheduling is that the incoming period is added, and the first one passed in is 0, so it is determined that this parameter is the time slice, not the number of times. Note that the period here adds a negative number, which is the inversion. That is, we start to pass in 1000, which will become -1000 when calling sched. In fact, after reading the source code, you will find that this is a kind of number for foreigners. Understanding

public void scheduleAtFixedRate(TimerTasktask,long delay,long period)

[code]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);
   }
Copy after login

The only difference is that there is no inversion in period,

[code]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();
        }
    }
Copy after login

queue is a queue, do During this operation, synchronization occurs, so at the timer level, this is thread-safe. Finally, the task-related parameters are assigned, mainly including nextExecutionTime (next execution time), period (time slice), state (state), and then Put it into the queue and perform a notify operation. Why do you need to perform a notify operation?

[code]class TaskQueue {

    private TimerTask[] queue = new TimerTask[128];

    private int size = 0;
Copy after login

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方法。

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

[code]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;
       }
   }
Copy after login

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

[code]public void cancel() {
        synchronized(queue) {
            thread.newTasksMayBeScheduled = false;
            queue.clear();
            queue.notify();  // In case queue was already empty.
        }
    }
Copy after login

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

[code]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;
     }
Copy after login

那么调度呢,是如何调度的呢,那些notify,和清空队列是如何做到的呢?我们就要看看TimerThread类了,内部有一个属性是:newTasksMayBeScheduled,也就是我们开始所提及的那个参数在cancel的时候会被设置为false。

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

[code]public void run() {
        try {
            mainLoop();
        } finally {
            synchronized(queue) {
                newTasksMayBeScheduled = false;
                queue.clear();  // Eliminate obsolete references
            }
        }
    }
Copy after login
[code]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&#39;t yet fired; wait
                        queue.wait(executionTime - currentTime);
                }
                if (taskFired)  // Task fired; run it, holding no locks
                    task.run();
            } catch(InterruptedException e) {
            }
        }
    }
Copy after login

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

hile (queue.isEmpty() &&newTasksMayBeScheduled) 

                        queue.wait();
Copy after login


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

if (queue.isEmpty()) 

    break;
Copy after login

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

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

[code]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);
     }
Copy after login


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

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

[code]if (!taskFired)// Taskhasn&#39;t yet fired; wait
    queue.wait(executionTime- currentTime);
Copy after login

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

[code] if (taskFired) // Task fired; run it, holding no locks
    task.run();
Copy after login

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

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

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

Executors.newScheduledThreadPool
Copy after login

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

[code]Executors.unconfigurableScheduledExecutorService
Copy after login


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

以上就是java-并发-Timer和TimerTask的内容,更多相关内容请关注PHP中文网(www.php.cn)!


Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
About us Disclaimer Sitemap
php.cn:Public welfare online PHP training,Help PHP learners grow quickly!