Maison > Java > javaDidacticiel > le corps du texte

java-concurrency-Timer et TimerTask

黄舟
Libérer: 2017-01-19 11:43:31
original
1227 Les gens l'ont consulté

Timer est un planificateur, et TimerTask est juste une classe qui implémente la méthode d'exécution, et la TimerTask spécifique doit être implémentée par vous-même

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

planification d'annulation publique (tâche TimerTask, long délai)

Cette méthode consiste à planifier une tâche. Après un délai (ms), la planification démarre et n'est planifiée qu'une seule fois.

Planification d'annulation publique (tâche TimerTask, Date heure)

Planification une fois à l'heure spécifiée.

planification publique nulle (tâche TimerTask, long délai, longue période)

Cette méthode consiste à planifier une tâche et à commencer la planification après un délai (ms). Après chaque planification, attendez au moins une période. (ms) avant le début de la planification.

planning public void (tâche TimerTask, Date firstTime, longue période)

 Semblable à la méthode précédente, la seule différence est que le deuxième paramètre transmis est l'heure du premier planning.

public void planningAtFixedRate (tâche TimerTask, long délai, longue période)

Planifiez une tâche, commencez la planification après un délai (ms), puis planifiez à nouveau après une période (ms), cela ressemble à la même méthode : le planning est le même. En fait, ce n'est pas le cas. Vous verrez plus tard dans le code source que lorsque le planning calcule l'heure d'exécution suivante, il utilise l'heure actuelle (obtenue avant l'exécution de la tâche). tranche de temps, tandis que la méthode planningAtFixedRate utilise la tranche de temps d'exécution actuelle (c'est-à-dire calculer l'heure qui doit être exécutée maintenant). La première est l'heure d'exécution réelle, tandis que la seconde est le moment théorique. si la tranche horaire planifiée est de 5 s, alors théoriquement elle sera à 5, 10, 15, 20. Ces tranches horaires sont planifiées, mais si elles ne le sont pas en raison de certaines réquisitions CPU, si elles ne sont pas planifiées une première fois avant le 8ème seconde, alors la prochaine fois calculée par la méthode de planification devrait être la 13ème seconde au lieu de la 10ème seconde, il est donc possible que la prochaine fois ce soit 20 secondes plus tard, elle sera programmée une ou plusieurs fois. La méthode planningAtFixedRate calcule théoriquement la prochaine heure à planifier pour le tri. Si la 8ème seconde est planifiée, alors l'heure calculée doit être la 10ème seconde. Par conséquent, elle est à 2 secondes de l'heure actuelle, elle sera donc planifiée en premier dans la. lors de la planification du tri des files d'attente, essayez donc de minimiser la situation de planification manquante.

[code]public Timer() {
    this("Timer-" + serialNumber());
}
Copier après la connexion

Le fil créé n'est pas le fil principal. Une fois le fil principal terminé, le minuteur se terminera automatiquement sans utiliser l'annulation pour terminer la fin du minuteur.

[code]public Timer(boolean isDaemon) {
    this("Timer-" + serialNumber(), isDaemon);
}
Copier après la connexion

Indiquez s'il s'agit d'un thread d'arrière-plan. Le thread d'arrière-plan se déconnectera automatiquement quand et seulement lorsque le processus se terminera.

[code]public Timer(String name, boolean isDaemon) {
      thread.setName(name);
      thread.setDaemon(isDaemon);
      thread.start();
  }
Copier après la connexion

Il y a un fil ici. Ce fil est évidemment un fil et est enveloppé dans la classe Timer. Jetons un coup d'œil à la définition de ce fil. Timer enveloppe un fil en interne et est utilisé pour faire. des choses indépendantes du monde extérieur, la planification des threads, et TimerThread est un type par défaut, qui ne peut pas être référencé par défaut et est utilisé par Timer lui-même.

private TaskQueue queue = new TaskQueue();
Copier après la connexion

threadReaper, c'est un type Object, il remplace simplement la méthode finalize. Il est utilisé pour recycler les informations correspondantes lors du garbage collection et effectuer une sauvegarde GC, c'est-à-dire lorsque le thread du timer est exécuté. en raison de S'il est mort pour une raison quelconque et n'a pas été annulé, les informations dans la file d'attente à l'intérieur doivent être effacées. Cependant, nous ne considérons généralement pas cette méthode, nous avons donc seulement besoin de savoir pour quoi Java écrit cette méthode.

Méthode de planification

programme d'annulation public (tâche TimerTask, long délai)

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

programme d'annulation public (tâche TimerTask, long délai, longue période)

[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);
    }
Copier après la connexion

appelle également la méthode sched pour terminer la planification. La seule différence par rapport à la méthode de planification ci-dessus est que la période entrante est ajoutée et que la première transmise est 0, donc ce paramètre est déterminé comme étant l'heure. slice. Au lieu du nombre de fois, veuillez noter que le point ici ajoute un nombre négatif, qui est l'inversion, c'est-à-dire que nous commençons à passer par 1000, qui deviendra -1000 lors de l'appel de sched. code source, vous constaterez qu'il s'agit d'un étranger Pour la compréhension d'un nombre

public void planningAtFixedRate(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);
   }
Copier après la connexion

La seule différence est qu'il y a pas d'inversion en période,

[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();
        }
    }
Copier après la connexion

queue est une file d'attente Lors de cette opération, la synchronisation se produit, donc au niveau du timer, c'est thread-safe Enfin, les paramètres liés à la tâche sont attribués, principalement. y compris nextExecutionTime (prochaine heure d'exécution), period (time slice) ), state (state), puis placez-le dans la file d'attente et effectuez une opération de notification. Pourquoi devez-vous effectuer une opération de notification ?

[code]class TaskQueue {

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

    private int size = 0;
Copier après la connexion

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;
       }
   }
Copier après la connexion

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

[code]public void cancel() {
        synchronized(queue) {
            thread.newTasksMayBeScheduled = false;
            queue.clear();
            queue.notify();  // In case queue was already empty.
        }
    }
Copier après la connexion

貌似仅仅将队列清空掉,然后设置了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;
     }
Copier après la connexion

那么调度呢,是如何调度的呢,那些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
            }
        }
    }
Copier après la connexion
[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) {
            }
        }
    }
Copier après la connexion

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

hile (queue.isEmpty() &&newTasksMayBeScheduled) 

                        queue.wait();
Copier après la connexion


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

if (queue.isEmpty()) 

    break;
Copier après la connexion

当跳出上面的循环后,如果是设置了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);
     }
Copier après la connexion


这里可以看到,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);
Copier après la connexion

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

[code] if (taskFired) // Task fired; run it, holding no locks
    task.run();
Copier après la connexion

如果线程需要执行,那么调用它的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
Copier après la connexion

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

[code]Executors.unconfigurableScheduledExecutorService
Copier après la connexion


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

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


Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal
À propos de nous Clause de non-responsabilité Sitemap
Site Web PHP chinois:Formation PHP en ligne sur le bien-être public,Aidez les apprenants PHP à grandir rapidement!