타이머는 JDK의 초기 API입니다. 우리는 일반적으로 Timer와 Timertask를 사용하여 NewsCheduledthreadpool이 나오기 전에 지연과 주기성으로 작업을 수행하지만 타이머에는 약간의 결함이 있습니다. 우리는 왜 그렇게 말합니까?
타이머는 모든 타이머 작업을 수행하기위한 고유 스레드 만 생성합니다. 타이머 작업의 실행이 시간이 많이 걸리면 다른 타이머스크의 정확성에 문제가 발생합니다. 예를 들어, 하나의 타이머 스로크가 10 초마다 실행되고 다른 타이머스크는 40ms마다 실행됩니다. 후속 작업이 완료된 후 반복 작업은 4 회 연속으로 호출되거나 4 개의 통화가 완전히 "손실"됩니다. 타이머의 또 다른 문제는 Timertask에서 확인되지 않은 예외를 던지면 타이머 스레드가 종료된다는 것입니다. 이 경우 타이머는 스레드의 실행에 다시 응답하지 않습니다. 전체 타이머가 취소되었다고 잘못 생각합니다. 예약되었지만 아직 실행되지 않은 Timertask는 다시 실행되지 않으며 새로운 작업을 예약 할 수 없습니다.
여기서 문제를 재현하기 위해 작은 데모를 만들었습니다. 코드는 다음과 같습니다.
패키지 com.hjc; import java.util.timer; import java.util.timertask;/*** Cong가 2018/7/12에 작성했습니다. */public class timertest {// 타이머 개체 생성 정적 타이머 타이머 = new Timer (); public static void main (string [] args) {// 작업 1, Delay Execution Timer.Schedule (new TimertAsk () {@override public void run () {system.out.println ( "-하나의 작업 ---"); thread.sleep (1000); {eorrupted exception () {eor-rustectecection (); ");}}, 500); // 작업 2, 지연 실행 timer.schedule (new Timertask () {@override public void run () {for (;;) {System.out.println ( "-두 작업 ---"); thread.sleep (1000);} catch (InterruptedException e) {// auto-genterated catchtrated catchtrated catchtrated bloc. }, 1000); }}위에서 언급했듯이, 500ms 이후에 실행하기 위해 작업이 먼저 추가 된 다음 두 번째 작업이 1 초 이후에 실행하도록 추가되었습니다. 우리가 기대하는 것은 첫 번째 작업이 하나의 작업을 출력 할 때 1S를 기다리는 경우 두 번째 작업이 출력 ----- 작업 ---,
그러나 코드를 실행 한 후 출력은 다음과 같습니다.
예 2,
공개 클래스 셰더 {개인 정적 롱 시작; public static void main (String [] args) {Timertask Task = new Timertask () {public void run () {System.out.println (System.CurrentTimeMillis ()-시작); try {thread.sleep (3000); } catch (InterruptedException e) {e.printstacktrace (); }}}; TIMERTASK TASK1 = new TimerTask () {@Override public void Run () {System.out.println (System.CurrentTimeMillis ()-시작); }}; 타이머 타이머 = 새로운 타이머 (); start = system.currenttimeMillis (); // 예정된 작업을 시작하고 Timer.Schedule (Task, 1000)을 실행합니다. // 예정된 작업을 시작하고 Timer.Schedule (Task1,3000)을 실행합니다. }}두 번째 작업이 3S, 즉 1000 및 1 3000을 출력 한 후 첫 번째 작업이 실행 된 후 위의 프로그램이 실행될 것으로 예상됩니다.
실제 작업 결과는 다음과 같습니다.
실제 작업 결과는 우리가 원하는대로 아닙니다. 세계의 결과는 두 번째 작업이 4 초 이후의 출력이라는 것입니다. 즉, 4001은 약 4 초입니다. 그 부분은 어디로 갔습니까? 그 시간은 우리의 첫 번째 과제의 수면으로 점령되었습니다.
이제 첫 번째 작업에서 Thread.sleep ()를 제거합니다. 이 코드 라인이 올바르게 실행 중입니까? 작업 결과는 다음과 같습니다.
첫 번째 작업은 1 초 이후에 실행되며 첫 번째 작업이 실행 된 후 3 초 후에 실행됩니다.
이것은 타이머가 모든 타이머 작업을 수행하기 위해 고유 스레드 만 생성한다는 것을 의미합니다. 타이머 작업의 실행이 시간이 많이 걸리면 다른 타이머스크의 정확성에 문제가 발생합니다.
타이머 구현 원리 분석
다음은 타이머의 원리에 대한 간단한 소개입니다. 다음 그림은 타이머의 원리 모델에 대한 소개입니다.
1. Taskqueue는 균형 바이너리 트리 힙으로 구현 된 우선 순위 대기열이며 각 타이머 객체에는 내부에 고유 한 Taskqueue 대기열이 있습니다. 사용자 스레드 호출 타이머의 일정 방법은 타임 스탁 작업을 Taskqueue 대기열에 추가하는 것입니다. 일정 방법을 호출 할 때, 긴 지연 매개 변수는 작업이 실행되도록 지연되는 시간을 나타내는 데 사용됩니다.
2. TIMERTHREAD는 특정 작업을 수행하는 스레드입니다. 실행을 위해 Taskqueue 대기열에서 우선 순위가 낮은 작업을 얻습니다. 현재 작업이 실행 된 후에 만 다음 작업을 큐에서 얻을 수 있습니다. 대기열에 설정 지연 시간이 있는지 여부에 관계없이 타이머에는 하나의 타이머 스레드 스레드가 있으므로 타이머의 내부 구현은 다중 프로듀서 단일 소비자 모델임을 알 수 있습니다.
구현 모델에서 위의 문제를 탐색하기 위해 TimerThread의 구현 만 살펴보기 만하면됩니다. Timerthread의 실행 방법의 기본 논리 소스 코드는 다음과 같습니다.
public void run () {try {mainLoop (); } 마침내 {// 누군가 가이 스레드를 죽였고, 타이머가 동기화 된 것처럼 작용합니다 (큐) {NewTaskSmayBescheduled = false; queue.clear (); // 오래된 참조를 제거}}} private void mainLoop () {while (true) {try {timertask task; 부울 태스크 피크; // 잠금 동기화 (queue) {......} if (taskfired) task.run (); // execute task} catch (InterpruptedException e) {}}}작업 실행 중에 InterruptedException 이외의 예외가 발생하면 예외를 던지기 때문에 유일한 소비자 스레드가 종료되며 큐에서 실행될 다른 작업이 지워집니다. 따라서 Try-Catch 구조를 사용하여 Timertask의 실행 방법에서 가능한 주요 예외를 포착하고 런 방법 외부에서 예외를 던지지 않는 것이 가장 좋습니다.
실제로 타이머와 유사한 함수를 구현하려면 ScheduledThreadPooleExecutor의 일정을 사용하는 것이 더 나은 선택입니다. ScheduledThreadPooleExecutor의 한 작업은 예외를 던지고 다른 작업은 영향을받지 않습니다.
ScheduledThreadPooleExecutor 예제는 다음과 같습니다.
/*** Cong에 의해 2018/7/12에 의해 만들어졌습니다. */public class scheduledThreadPooleExecutorTest {static scheduledtheRreadPooleExecutor ScheduledThreadPooleExecutor = new ScheduledThreadPooleExecutor (1); public static void main (string [] args) {scheduledtheRreadpoolexecutor.schedule (new runnable () {public void run () {system.out.println ( "-하나의 작업 ---"); try {thread.sleep (1000); e. }, 500, TimeUnit.microseconds); scheduledthreadpoolexecutor.schedule (new runnable () {public void run () {for (int i = 0; i <5; ++ i) {system.out.println ( "-두 작업 ---"); thread.sleep (1000); TimeUnit.microseconds); ScheduledThreadPoolExecutor.shutdown (); }}작업 결과는 다음과 같습니다.
ScheduledThreadPooleExecutor의 다른 작업이 예외를 던지는 작업에 영향을받지 않는 이유는 캐치가 ScheduledThreadPooleExecutor의 ScheduledFutureTask 작업에서 예외를 떨어 뜨리기 때문에 스레드 풀 작업의 실행 방법에서 예외 및 인쇄 로그를 인쇄하는 데 걸리는 것이 가장 좋습니다.