java.util.Timer 소스 코드 분석
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 를 도입 해 야 한 다 는 것 을 설명 했다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
JPA + QueryDSL 계층형 댓글, 대댓글 구현(2)이번엔 전편에 이어서 계층형 댓글, 대댓글을 다시 리팩토링해볼 예정이다. 이전 게시글에서는 계층형 댓글, 대댓글을 구현은 되었지만 N+1 문제가 있었다. 이번에는 그 N+1 문제를 해결해 볼 것이다. 위의 로직은 이...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.