Heim  >  Artikel  >  Java  >  Java-Concurrency-Timer und TimerTask

Java-Concurrency-Timer und TimerTask

黄舟
黄舟Original
2017-01-19 11:43:311203Durchsuche

Timer ist ein Scheduler, und TimerTask ist nur eine Klasse, die die Ausführungsmethode implementiert, und die spezifische TimerTask muss von Ihnen selbst implementiert werden

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

öffentlicher Void-Zeitplan (TimerTask-Aufgabe, lange Verzögerung)

Bei dieser Methode wird eine Aufgabe nach einer Verzögerung (ms) geplant und nur einmal geplant.

Public Void Schedule (TimerTask Task, Date Time)

Einmalige Planung zum angegebenen Zeitpunkt.

Public Void Schedule (TimerTask Task, Long Delay, Long Period)

Diese Methode besteht darin, eine Aufgabe zu planen und nach jeder Verzögerung (ms) mit der Planung zu beginnen (ms) bevor die Planung beginnt.

Public Void Schedule(TimerTask Task, Date FirstTime, Long Period)

 Ähnlich wie bei der vorherigen Methode besteht der einzige Unterschied darin, dass der zweite übergebene Parameter die Zeit des ersten Zeitplans ist.

öffentlicher Void-ScheduleAtFixedRate(TimerTask-Aufgabe, lange Verzögerung, langer Zeitraum)

Planen Sie eine Aufgabe, beginnen Sie mit der Planung nach einer Verzögerung (ms) und planen Sie dann erneut nach einem Zeitraum (ms), wie es aussieht die gleiche Methode: Tatsächlich ist dies nicht der Fall. Aus dem Quellcode werden Sie später ersehen, dass der Zeitplan die aktuelle Zeit verwendet (die vor der Ausführung der Aufgabe ermittelt wurde). + Zeitscheibe, während die Methode „scheduleAtFixedRate“ die aktuelle Ausführungszeit (dh die berechnete Zeit, die jetzt ausgeführt werden soll) + Zeitscheibe verwendet, erstere ist die tatsächliche Laufzeit und letztere ist die theoretische Zum Beispiel: Die geplante Zeitscheibe beträgt 5 Sekunden, dann liegt sie theoretisch bei 5, 10. Diese Zeitscheiben 15 und 20 sind geplant, aber wenn sie aufgrund einer CPU-Anforderung nicht geplant sind, sind sie nicht geplant Das erste Mal bis zur 8. Sekunde, dann sollte die nächste nach der Zeitplanmethode berechnete Zeit die 13. Sekunde statt der 10. Sekunde sein. Auf diese Weise ist es möglich, dass das nächste Mal 20 Sekunden später geplant wird Die Methode „scheduleAtFixedRate“ berechnet theoretisch den nächsten Zeitpunkt, der für die Sortierung geplant ist. Wenn die 8. Sekunde geplant ist, sollte die berechnete Zeit 10 Sekunden betragen, also 2 Sekunden von der aktuellen Zeit entfernt sein, dann wird sie zuerst geplant Versuchen Sie daher, die Situation fehlender Planung zu minimieren.

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

Der erstellte Thread ist nicht der Hauptthread. Nach dem Ende des Hauptthreads wird der Timer automatisch beendet, ohne dass Abbrechen verwendet werden muss, um das Ende des Timers abzuschließen.

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

Geben Sie ein, ob es sich um einen Hintergrundthread handelt, der sich nur dann automatisch abmeldet, wenn der Prozess endet.

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

Hier gibt es einen Thread, der in die Timer-Klasse eingebunden ist. Schauen wir uns die Definition dieses Threads an Dinge unabhängig von der Außenwelt, und TimerThread ist ein Standardtyp, der standardmäßig nicht referenziert werden kann und vom Timer selbst verwendet wird.

private TaskQueue queue = new TaskQueue();

threadReaper, es ist ein Objekttyp, er überschreibt lediglich die Finalize-Methode. Er wird verwendet, um die entsprechenden Informationen während der Speicherbereinigung zu recyceln und eine GC-Sicherung durchzuführen, d. h. wenn der Timer-Thread vorhanden ist Aufgrund von Wenn es aus irgendeinem Grund gestorben ist und nicht abgebrochen wurde, müssen die Informationen in der Warteschlange darin gelöscht werden. Normalerweise berücksichtigen wir diese Methode jedoch nicht, sodass wir nur wissen müssen, wofür Java diese Methode schreibt.

Planungsmethode

Öffentlicher Löschplan (TimerTask-Aufgabe, lange Verzögerung)

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

Öffentlicher Löschplan (TimerTask-Aufgabe, lange Verzögerung, langer Zeitraum)

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

Ruft auch die Methode sched auf, um die Planung abzuschließen. Der einzige Unterschied zur obigen Methode besteht darin, dass die eingehende Periode hinzugefügt wird und die erste übergebene Periode 0 ist, sodass dieser Parameter als Zeit bestimmt wird Bitte beachten Sie, dass der Punkt hier anstelle der Anzahl eine negative Zahl hinzufügt. Das heißt, wir beginnen mit der Übergabe von 1000, die beim Aufruf von sched tatsächlich zu -1000 wird Im Quellcode werden Sie feststellen, dass dies ein Ausländer ist. Zum Verständnis einer Zahl

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

Der einzige Unterschied besteht darin, dass es einen gibt Keine Invertierung im Zeitraum,

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

Warteschlange ist eine Warteschlange. Bei dieser Operation erfolgt eine Synchronisierung, sodass auf Timer-Ebene die aufgabenbezogenen Parameter hauptsächlich zugewiesen werden einschließlich nextExecutionTime (nächste Ausführungszeit), Zeitraum (Zeitscheibe), Status (Status) und dann in die Warteschlange stellen und einen Benachrichtigungsvorgang ausführen. Warum müssen Sie einen Benachrichtigungsvorgang ausführen?

[code]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方法。 

  对于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;
       }
   }

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

[code]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方法来完成顺序的重排,源码如下:

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

那么调度呢,是如何调度的呢,那些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
            }
        }
    }
[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) {
            }
        }
    }

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

hile (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设置最新时间并排序: 

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


这里可以看到,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);

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

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

如果线程需要执行,那么调用它的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

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

[code]Executors.unconfigurableScheduledExecutorService


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

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


Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn