> Java > java지도 시간 > 본문

java-concurrency-Timer 및 TimerTask

黄舟
풀어 주다: 2017-01-19 11:43:31
원래의
1227명이 탐색했습니다.

Timer는 스케줄러이고 TimerTask는 run 메소드를 구현하는 클래스일 뿐이며 특정 TimerTask는 직접 구현해야 합니다.

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

public void Schedule(TimerTask 작업, 긴 지연 )

이 방법은 작업을 예약하고 지연(ms) 후에 예약을 시작하는 것입니다.

공용 무효 스케줄(TimerTask 태스크, 날짜 시간)

지정된 시점 시간에 한 번 스케줄링합니다.

공개 무효 일정(TimerTask 작업, 긴 지연, 긴 기간)

이 방법은 작업을 예약하고 지연(ms) 후에 예약을 시작하는 방법입니다. (ms) 일정이 시작되기 전입니다.

public void 일정(TimerTask task, Date firstTime, long period)

 이전 방법과 유사하지만 두 번째 매개변수에 첫 번째 일정의 시간이 전달된다는 점만 다릅니다.

public void ScheduleAtFixedRate(TimerTask 작업, 긴 지연, 긴 기간)

작업을 예약하고 지연(ms) 후에 예약을 시작한 다음 기간(ms) 후에 다시 예약하면 다음과 같습니다. 동일한 방법: 실제로는 그렇지 않습니다. 나중에 소스 코드를 보면 일정이 다음 실행 시간을 계산할 때 현재 시간(작업이 실행되기 전에 얻은 시간)을 사용한다는 것을 알 수 있습니다. + 타임 슬라이스, ScheduleAtFixedRate 메소드는 현재 시간을 사용하지만 실행에 필요한 시간(즉, 지금 실행해야 하는 계산된 시간) + 타임 슬라이스, 전자는 실제 실행 시간이고 후자는 이론적인 시간입니다. 예를 들어 일정 시간 조각은 5초이고 이론적으로는 5, 10이 됩니다. 이러한 시간 조각 15와 20은 예약되어 있지만 일부 CPU 요청으로 인해 예약되지 않은 경우에는 예약되지 않습니다. 첫 번째 시간은 8초까지이고 다음 시간은 10초가 아닌 13초로 계산해야 다음 번에는 20초 후에 예약될 가능성이 있습니다. ScheduleAtFixedRate 메소드는 이론적으로 정렬을 예약해야 하는 다음 시간을 계산합니다. 8초가 예약된 경우 계산된 시간은 10초여야 하므로 현재 시간에서 2초 차이가 납니다. 재예약 대기열 정렬에서 가장 먼저 예약되므로 예약이 누락되는 상황을 최소화하도록 노력하세요.

[code]public Timer() {
    this("Timer-" + serialNumber());
}
로그인 후 복사

가 생성한 스레드는 메인 스레드가 아니며, 타이머 종료를 완료하기 위해 취소를 사용하지 않고도 타이머가 자동으로 종료됩니다.

[code]public Timer(boolean isDaemon) {
    this("Timer-" + serialNumber(), isDaemon);
}
로그인 후 복사

백그라운드 스레드인지 여부를 입력합니다. 백그라운드 스레드는 프로세스가 종료될 때만 자동으로 로그아웃됩니다.

[code]public Timer(String name, boolean isDaemon) {
      thread.setName(name);
      thread.setDaemon(isDaemon);
      thread.start();
  }
로그인 후 복사

여기에 스레드가 있습니다. 이 스레드는 분명히 스레드이며 Timer 클래스에 래핑되어 있습니다. 타이머는 내부적으로 스레드를 래핑하고 독립적인 작업을 수행하는 데 사용됩니다. Scheduling, TimerThread는 기본 유형으로 기본적으로 참조할 수 없으며 Timer 자체에서 사용됩니다.

private TaskQueue queue = new TaskQueue();
로그인 후 복사

threadReaper는 객체 유형이며 finalize 메서드를 재정의할 뿐입니다. 가비지 수집 중에 해당 정보를 재활용하고 GC 백업을 수행하는 데 사용됩니다. 어떤 종류의 이유는 취소되지 않고 죽고 큐에 있는 정보를 지워야 하기 때문입니다. 그러나 우리는 일반적으로 이 메소드를 고려하지 않으므로 Java가 이 메소드를 작성하는 목적만 알면 됩니다.

스케줄링 방법

공용 무효 일정(TimerTask 작업, 장기 지연)

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

공공 무효 일정(TimerTask 작업, 장기 지연, 장기)

[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);
    }
로그인 후 복사

스케줄링을 완료하기 위해 sched 메소드도 호출됩니다. 스케줄링과 위 방법의 유일한 차이점은 들어오는 기간이 추가되고 전달된 첫 번째 기간이 0이므로 이 매개변수가 타임 슬라이스로 결정됩니다. 여기서 마침표는 음수, 즉 반전을 추가한다는 점에 유의하세요. 즉, 1000을 전달하기 시작하며 실제로 소스 코드를 읽은 후에는 -1000이 됩니다.

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);
   }
로그인 후 복사

를 이해하면 유일한 차이점은 기간에 반전이 없다는 것입니다. ,

[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();
        }
    }
로그인 후 복사

queue는 큐, do 이 작업 중에 동기화가 발생하므로 타이머 수준에서는 스레드로부터 안전합니다. 마지막으로 주로 nextExecutionTime(다음 실행 시간)을 포함하여 작업 관련 매개 변수가 할당됩니다. ), 기간(Time Slice), 상태(State)를 차례로 큐에 넣고 알림 작업을 수행해야 하는 이유는 무엇입니까?

[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)!


관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿
회사 소개 부인 성명 Sitemap
PHP 중국어 웹사이트:공공복지 온라인 PHP 교육,PHP 학습자의 빠른 성장을 도와주세요!