java.util.Timer 소스 코드 분석

22976 단어 Java
머리말
java.util.Timer 는 JDK 가 제공 하 는 정시 작업 실행 기 입 니 다.타이머 에 정시 작업 을 추가 하고 기한 내 에 수행 할 수 있 습 니 다.Timer 를 사용 하려 면 먼저 Timer 의 인 스 턴 스 를 만들어 야 합 니 다.인 스 턴 스 를 만 든 후 schedule 방법 으로 작업 을 만 들 수 있 습 니 다.Timer 의 정시 작업 은 대상 TimeTask 로 사용자 가 TimeTask 의 run 방법 을 다시 써 서 자신의 작업 을 정의 해 야 합 니 다.또한 Timer 는 라인 이 안전 한 클래스 입 니 다.
Timer 는 결함 이 있 는 정시 작업 수행 기 입 니 다.새 코드 에 서 는 더 이상 사용 을 추천 하지 않 습 니 다.현재 Scheduled Executor Service 로 대체 되 었 습 니 다.다음은 원본 코드 를 통 해 결함 이 어디 에 있 는 지 분석 하 겠 습 니 다.
2.소스 코드 분석
먼저 Timer 클래스 의 클래스 정의 와 인 스 턴 스 변 수 를 봅 니 다.
//      
private final TaskQueue queue = new TaskQueue();
//         
private final TimerThread thread = new TimerThread(queue);
//     Timer   , GC    finalize      
private final Object threadReaper = new Object() {
    protected void finalize() throws Throwable {
        synchronized(queue) {
            thread.newTasksMayBeScheduled = false;
            queue.notify();
        }
    }
};

Timer 클래스 는 세 개의 인 스 턴 스 변수 만 있 습 니 다.각각 정시 작업 을 저장 하 는 대기 열 입 니 다.정시 작업 을 수행 하 는 스 레 드 와 자원 을 방출 하 는 Object 대상 입 니 다.Timer Thread 는 Thread 의 하위 클래스 입 니 다.Timer 류 는 4 가지 Public 구조 방법 을 제공 합 니 다.
public Timer() {
    this("Timer-" + serialNumber());
}

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

public Timer(String name) {
    thread.setName(name);
    thread.start();
}

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

Timer 클래스 를 구성 할 때 Timer 의 이름 을 지정 할 수도 있 고,이 정시 작업 을 수행 하 는 라인 을 백 엔 드 라인(수호 라인)으로 설정 할 수도 있 으 며,지정 하지 않 으 면 기본 값 은 백 엔 드 라인 이 아 닙 니 다.이름 을 지정 하지 않 으 면 Timer 의 이름 을 하나의 숫자 로 설정 합 니 다.이 숫자 는 현재 스 레 드 컨 텍스트 클래스 로 더 가 몇 개의 Timer 를 초기 화 했 는 지 에 따라 결 정 됩 니 다.
private final static AtomicInteger nextSerialNumber = new AtomicInteger(0);
private static int serialNumber() {
    return nextSerialNumber.getAndIncrement();
}

구조 가 완료 되면 정시 작업 수행 스 레 드 도 시 작 됩 니 다.
java.util.Timer 클래스 는 여러 가지 방법 으로 정시 작업 을 제출 합 니 다.
// delay       
public void schedule(TimerTask task, long delay);
//    time(Date          )    
public void schedule(TimerTask task, Date time);
// delay       ,               period        
public void schedule(TimerTask task, long delay, long period);
// firstTime        period        
public void schedule(TimerTask task, Date firstTime, long period);
// delay       ,  period           
public void scheduleAtFixedRate(TimerTask task, long delay, long period);
//     firstTime    ,            period      
public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period);

TimeTask 는 추상 적 인 클래스 로 작업 을 정의 할 때 TimeTask 의 하위 클래스 를 만 들 고 run 방법 을 다시 써 야 합 니 다.run 방법 은 작업 논리 입 니 다.schedule(TimeTask,long,long)과 scheduleAtFixed Rate(TimeTask,long,long)의 공통점 은 delay 밀리초 에 임 무 를 수행 하 는 것 입 니 다.다른 점 은 다음 임 무 를 수행 하 는 시간 계산 방식 입 니 다.1.schedule 방법 은 다음 임 무 를 수행 하 는 시간 을 계산 할 때 작업 스 레 드 가 실제 수행 하 는 시간 을 기준 으로 period 밀리초 를 추가 합 니 다.2.scheduleAtFixed Rate 방법 은 다음 작업 수행 시간 을 계산 할 때 delay+n*period 시간 스탬프 에 따라 엄 격 히 수행 합 니 다.
이러한 Public 방법 은 예외 없 이 Timer 내부 의 개인 적 인 방법 sched 를 호출 합 니 다.sched 방법 을 알 기 전에 먼저 TimeTask 류 와 작업 수행 대기 열 TaskQueue 의 소스 코드 를 알 아 볼 필요 가 있 습 니 다.
TimerTask
public abstract class TimerTask implements Runnable {
    static final int VIRGIN = 0; //     
    static final int SCHEDULED = 1; //     ,    Timer      
    static final int EXECUTED = 2; //       (                )
    static final int CANCELLED = 3; //         

    //   
    final Object lock = new Object();
    //       VIRGIN
    int state = VIRGIN;
    //        
    long nextExecutionTime;
    //      ,0     
    long period = 0;
    //      
    public abstract void run();

    //    ,        
    public boolean cancel() {
        synchronized(lock) {
            boolean result = (state == SCHEDULED);
            state = CANCELLED;
            return result;
        }
    }  
    //         
    public long scheduledExecutionTime() {
        synchronized(lock) {
            return (period < 0 ? nextExecutionTime + period 
                    : nextExecutionTime - period);
        }
    }
}

Timer Task 는 작업 의 상태,실행 시작 시간,작업 순환 주 기 를 저장 합 니 다.하위 클래스 는 작업 수행 코드 를 run 방법 에 다시 쓸 수 있 습 니 다.
TaskQueue TaskQueue 는 TimeTask 작업 대상 을 저장 하 는 데 사 용 됩 니 다.
class TaskQueue {
    //         128     
    private TimerTask[] queue = new TimerTask[128];
    //    
    private int size = 0;

    int size() {
        return size;
    }

    //  TimerTask    
    void add(TimerTask task) {
        //          queue     queue      2 
        if (size + 1 == queue.length)
            queue = Arrays.copyOf(queue, 2*queue.length);
        //     
        queue[++size] = task;
        //      
        fixUp(size);
    }

    //              TimerTask  queue[1](      )
    private void fixUp(int k) {
        while (k > 1) {
            int j = k >> 1;
            if (queue[j].nextExecutionTime <= queue[k].nextExecutionTime)
                break;
            //  queue[j] queue[k]
            TimerTask tmp = queue[j];  queue[j] = queue[k]; queue[k] = tmp;
            k = j;
        }
    }
    //               TimerTask
    TimerTask getMin() {
        return queue[1];
    }

    //    i      TimeTask
    TimerTask get(int i) {
        return queue[i];
    }

    //        TimerTask
    void removeMin() {
        queue[1] = queue[size];
        queue[size--] = null;
        //    
        fixDown(1);
    }

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

    //    TimerTask,          
    void quickRemove(int i) {
        assert i <= size;
        queue[i] = queue[size];
        queue[size--] = null;
    }

    //       TimerTask      newTime, TimerThread    
    void rescheduleMin(long newTime) {
        queue[1].nextExecutionTime = newTime;
        //    
        fixDown(1);
    }

    //          
    boolean isEmpty() {
        return size == 0;
    }

    //     TimerTask
    void clear() {
        for (int i = 1; i <= size; i++)
            queue[i] = null;
        size = 0;
    }
}

TaskQueue 는 우선 대기 열 을 사용 하여 quue[1]가 항상 시간 스탬프 가 가장 작은 Timer Task 를 보장 합 니 다.
Timer Task 와 Task Queue 의 소스 코드 를 소개 한 후에 우 리 는 다시 고 개 를 돌려 sched 방법 을 분석 합 니 다.
//time          ,period  0   scheduleAtFixedRate       ,
//  0         ,  0  schedule         
private void sched(TimerTask task, long time, long period) {
    if (time < 0)
        throw new IllegalArgumentException("Illegal execution time.");
    if (Math.abs(period) > (Long.MAX_VALUE >> 1))
        period >>= 1;
    //      ,      
    synchronized(queue) {
        //                  
        if (!thread.newTasksMayBeScheduled)
            throw new IllegalStateException("Timer already cancelled.");
        synchronized(task.lock) {
            //  TimerTask           
            if (task.state != TimerTask.VIRGIN)
                throw new IllegalStateException("Task already scheduled or cancelled");
            //         
            task.nextExecutionTime = time;
            //             , 0    
            task.period = period;
            //        
            task.state = TimerTask.SCHEDULED;
        }
        //       
        queue.add(task);
        //                  ,        
        if (queue.getMin() == task)
            queue.notify();
    }
}

Timer 클래스 의 개인 적 인 방법 sched 는 Timer 가 가지 고 있 는 작업 대기 열 에 작업 을 추가 할 수 있 습 니 다.Timer Task 의 run 방법 은 Timer Thead 가 수행 합 니 다.모든 Timer 는 하나의 Timer Thread 스 레 드 만 이 run 방법 을 호출 합 니 다.
class TimerThread extends Thread {
    //  Timer      ,     Timer cancel      false
    boolean newTasksMayBeScheduled = true;
    //Timer       
    private TaskQueue queue;

    // Timer            
    TimerThread(TaskQueue queue) {
        this.queue = queue;
    }

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

    private void mainLoop() {
        while (true) {
            try {
                TimerTask task;
                boolean taskFired;
                synchronized(queue) {
                    //         Timer    ,                 
                    while (queue.isEmpty() && newTasksMayBeScheduled)
                        queue.wait();
                    //         ,  Timer    ,       
                    if (queue.isEmpty())
                        break;
                    long currentTime, executionTime;
                    //           
                    task = queue.getMin();
                    synchronized(task.lock) {
                        //        ,                    
                        if (task.state == TimerTask.CANCELLED) {
                            queue.removeMin();
                            continue; 
                        }
                        //                
                        currentTime = System.currentTimeMillis();
                        //           
                        executionTime = task.nextExecutionTime;
                        //                 ,  taskFired  true,       
                        if (taskFired = (executionTime<=currentTime)) {
                            //           
                            if (task.period == 0) {
                                //          
                                queue.removeMin();
                                //          
                                task.state = TimerTask.EXECUTED;
                            } else {
                                //                  
                                //schedule  scheduleAtFixedRate  
                                queue.rescheduleMin(
                                  task.period<0 ? currentTime - task.period
                                                : executionTime + task.period);
                            }
                        }
                    }
                    //    ,                    
                    if (!taskFired)
                        queue.wait(executionTime - currentTime);
                }
                //    
                if (taskFired)
                    task.run();
            } catch(InterruptedException e) {
                //      ,       
            }
        }
    }
}

TimerThread 는 두 가지 문제 가 있 습 니 다.1.작업 에 처리 되 지 않 은 이상 이 있 으 면 TimeThread 스 레 드 가 종 료 됩 니 다.이 어 Timer 가 속 한 작업 대기 열 에 있 는 작업 을 계속 수행 할 수 없습니다.2.퀘 스 트 대기 열 에 얼마나 많은 퀘 스 트 가 있 든 지 간 에 이 퀘 스 트 들 은 하나의 스 레 드 로 만 직렬 적 으로 실 행 될 수 있 습 니 다.한 퀘 스 트 의 수행 시간 이 길 거나 퀘 스 트 의 수량 이 너무 많은 상황 에서 퀘 스 트 가 기한 내 에 실 행 될 수 있다 는 것 을 보장 하지 못 할 수도 있 습 니 다.
이것들 은 사실 Timer 류 의 두 가지 결함 이다.
/* 

Java 5.0 introduced the {@code java.util.concurrent} package and * one of the concurrency utilities therein is the {@link * java.util.concurrent.ScheduledThreadPoolExecutor * ScheduledThreadPoolExecutor} which is a thread pool for repeatedly * executing tasks at a given rate or delay. It is effectively a more * versatile replacement for the {@code Timer}/{@code TimerTask} * combination, as it allows multiple service threads, accepts various * time units, and doesn't require subclassing {@code TimerTask} (just * implement {@code Runnable}). Configuring {@code * ScheduledThreadPoolExecutor} with one thread makes it equivalent to * {@code Timer}.*/


Timer 류 의 주석 도 JDK 1.5 에 Scheduled Executor Service 를 도입 해 야 한 다 는 것 을 설명 했다.

좋은 웹페이지 즐겨찾기