자바 타이머

1. 타이머 – 기본

TimerTimerTask 는 백그라운드 스레드에서 작업을 예약하는 데 사용되는 Java 유틸리티 클래스입니다. 간단히 말해서 – TimerTask 는 수행 할 작업이고 Timer 는 스케줄러 입니다.

2. 작업을 한 번 예약

2.1. 주어진 지연 후

Timer를 사용하여 단일 작업실행하는 것으로 시작하겠습니다 .

@Test public void givenUsingTimer_whenSchedulingTaskOnce_thenCorrect() { TimerTask task = new TimerTask() { public void run() { System.out.println("Task performed on: " + new Date() + "n" + "Thread's name: " + Thread.currentThread().getName()); } }; Timer timer = new Timer("Timer"); long delay = 1000L; timer.schedule(task, delay); }

이제 schedule () 메서드 의 두 번째 매개 변수로 지정된 특정 지연 후 작업을 수행합니다 . 다음 섹션에서는 주어진 날짜와 시간에 작업을 예약하는 방법을 살펴 보겠습니다.

JUnit 테스트 를 실행하는 경우 Junit 테스트가 실행을 중지하기 전에 Timer의 스레드가 작업을 실행할 수 있도록 Thread.sleep (delay * 2) 호출을 추가해야합니다 .

2.2. 주어진 날짜와 시간에

이제 두 번째 매개 변수로 long 대신 Date를 사용 하는 Timer # schedule (TimerTask, Date) 메서드를 살펴보면 지연 이후가 아닌 특정 순간에 작업을 예약 할 수 있습니다.

이번에는 이전 레거시 데이터베이스가 있고 더 나은 스키마를 사용하여 데이터를 새 데이터베이스로 마이그레이션하려고한다고 가정 해 보겠습니다.

마이그레이션을 처리 할 DatabaseMigrationTask 클래스를 만들 수 있습니다 .

public class DatabaseMigrationTask extends TimerTask { private List oldDatabase; private List newDatabase; public DatabaseMigrationTask(List oldDatabase, List newDatabase) { this.oldDatabase = oldDatabase; this.newDatabase = newDatabase; } @Override public void run() { newDatabase.addAll(oldDatabase); } }

간단히하기 위해 List of String으로 두 데이터베이스를 나타냅니다 . 간단히 말해, 마이그레이션은 첫 번째 목록의 데이터를 두 번째 목록에 넣는 것으로 구성됩니다.

원하는 순간에이 마이그레이션을 수행하려면 schedule () 메서드 의 오버로드 된 버전을 사용해야합니다 .

List oldDatabase = Arrays.asList("Harrison Ford", "Carrie Fisher", "Mark Hamill"); List newDatabase = new ArrayList(); LocalDateTime twoSecondsLater = LocalDateTime.now().plusSeconds(2); Date twoSecondsLaterAsDate = Date.from(twoSecondsLater.atZone(ZoneId.systemDefault()).toInstant()); new Timer().schedule(new DatabaseMigrationTask(oldDatabase, newDatabase), twoSecondsLaterAsDate);

보시다시피 schedule () 메서드에 마이그레이션 작업과 실행 날짜를 제공합니다 .

그런 다음 twoSecondsLater로 표시된 시간에 마이그레이션이 실행됩니다 .

while (LocalDateTime.now().isBefore(twoSecondsLater)) { assertThat(newDatabase).isEmpty(); Thread.sleep(500); } assertThat(newDatabase).containsExactlyElementsOf(oldDatabase);

이 순간 이전에는 마이그레이션이 발생하지 않습니다.

3. 반복 가능한 작업 예약

작업의 단일 실행을 예약하는 방법을 다루었으므로 이제 반복 가능한 작업을 처리하는 방법을 살펴 보겠습니다.

다시 한 번, Timer 클래스가 제공하는 여러 가능성이 있습니다. 고정 지연 또는 고정 속도를 관찰하도록 반복을 설정할 수 있습니다.

고정 지연은 지연된 경우에도 마지막 실행이 시작된 후 일정 시간 후에 실행이 시작됨을 의미합니다 (따라서 자체적으로 지연됨) .

2 초마다 일부 작업을 예약하고 첫 번째 실행에는 1 초가 걸리고 두 번째 실행에는 2 초가 걸리지 만 1 초 지연된다고 가정 해 보겠습니다. 그런 다음 세 번째 실행은 다섯 번째 초에 시작됩니다.

0s 1s 2s 3s 5s |--T1--| |-----2s-----|--1s--|-----T2-----| |-----2s-----|--1s--|-----2s-----|--T3--|

반면 고정 비율은 이전 실행이 지연 되더라도 각 실행이 초기 일정을 준수 함을 의미합니다 .

고정 된 속도로 이전 예제를 다시 사용하겠습니다. 두 번째 작업은 지연으로 인해 3 초 후에 시작됩니다. 그러나 세 번째는 4 초 후 (2 초마다 한 번 실행되는 초기 일정을 준수 함) :

0s 1s 2s 3s 4s |--T1--| |-----2s-----|--1s--|-----T2-----| |-----2s-----|-----2s-----|--T3--|

이 두 가지 원칙을 다룹니다. 사용 방법을 살펴 보겠습니다.

고정 지연 스케줄링을 사용하기 위해 schedule () 메서드 의 오버로드가 두 개 더 있으며 , 각각은 밀리 초 단위의주기를 나타내는 추가 매개 변수를 사용합니다.

왜 두 번의 과부하입니까? 특정 순간 또는 특정 지연 후에 작업을 시작할 가능성이 여전히 있기 때문입니다.

고정 속도 스케줄링에 관해서는 밀리 초 단위의주기를 취하는 두 가지 scheduleAtFixedRate () 메서드가 있습니다. 다시 말하지만, 주어진 날짜와 시간에 작업을 시작하는 방법과 주어진 지연 후에 작업을 시작하는 방법이 있습니다.

또한 작업 실행 시간보다 더 많은 시간이 소요되면 고정 지연 또는 고정 속도를 사용하든 전체 실행 체인이 지연된다는 점도 언급 할 가치가 있습니다.

3.1. 고정 지연

이제 매주 팔로워에게 이메일을 보내는 뉴스 레터 시스템을 구현하고 싶다고 가정 해 봅시다. 이 경우 반복적 인 작업이 이상적입니다.

그러니 매초마다 뉴스 레터를 예약 해 보겠습니다. 기본적으로는 스팸이지만 전송이 가짜이기 때문에 괜찮습니다!

먼저 NewsletterTask를 디자인 해 보겠습니다 .

public class NewsletterTask extends TimerTask { @Override public void run() { System.out.println("Email sent at: " + LocalDateTime.ofInstant(Instant.ofEpochMilli(scheduledExecutionTime()), ZoneId.systemDefault())); } }

작업이 실행될 때마다 작업은 TimerTask # scheduledExecutionTime () 메서드를 사용하여 수집 한 예약 된 시간을 인쇄합니다 .

그렇다면 고정 지연 모드에서 매초이 작업을 예약하려면 어떻게해야합니까? 앞서 언급 한 schedule () 의 오버로드 된 버전을 사용해야합니다 .

new Timer().schedule(new NewsletterTask(), 0, 1000); for (int i = 0; i < 3; i++) { Thread.sleep(1000); }

물론, 우리는 몇 가지 경우에만 테스트를 수행합니다.

Email sent at: 2020-01-01T10:50:30.860 Email sent at: 2020-01-01T10:50:31.860 Email sent at: 2020-01-01T10:50:32.861 Email sent at: 2020-01-01T10:50:33.861

보시다시피 각 실행 사이에 최소 1 초가 있지만 때때로 1 밀리 초 지연됩니다. 이 현상은 고정 지연 반복을 사용하기로 결정했기 때문입니다.

3.2. 고정 요금으로

이제 고정 속도 반복을 사용한다면 어떨까요? 그런 다음 scheduleAtFixedRate () 메서드 를 사용해야합니다 .

new Timer().scheduleAtFixedRate(new NewsletterTask(), 0, 1000); for (int i = 0; i < 3; i++) { Thread.sleep(1000); }

이번에는 실행이 이전 실행에 의해 지연되지 않습니다 .

Email sent at: 2020-01-01T10:55:03.805 Email sent at: 2020-01-01T10:55:04.805 Email sent at: 2020-01-01T10:55:05.805 Email sent at: 2020-01-01T10:55:06.805

3.3. 일일 작업 예약

다음 으로 하루에 한 번 작업을 실행 해 보겠습니다 .

@Test public void givenUsingTimer_whenSchedulingDailyTask_thenCorrect() { TimerTask repeatedTask = new TimerTask() { public void run() { System.out.println("Task performed on " + new Date()); } }; Timer timer = new Timer("Timer"); long delay = 1000L; long period = 1000L * 60L * 60L * 24L; timer.scheduleAtFixedRate(repeatedTask, delay, period); }

4. 타이머TimerTask 취소

작업 실행은 몇 가지 방법으로 취소 할 수 있습니다.

4.1. TimerTask Inside Run 취소

TimerTask 자체 의 run () 메서드 구현 내 에서 TimerTask.cancel () 메서드 를 호출합니다 .

@Test public void givenUsingTimer_whenCancelingTimerTask_thenCorrect() throws InterruptedException { TimerTask task = new TimerTask() { public void run() { System.out.println("Task performed on " + new Date()); cancel(); } }; Timer timer = new Timer("Timer"); timer.scheduleAtFixedRate(task, 1000L, 1000L); Thread.sleep(1000L * 2); }

4.2. 타이머 취소

Timer 객체 에서 Timer.cancel () 메서드 를 호출 합니다.

@Test public void givenUsingTimer_whenCancelingTimer_thenCorrect() throws InterruptedException { TimerTask task = new TimerTask() { public void run() { System.out.println("Task performed on " + new Date()); } }; Timer timer = new Timer("Timer"); timer.scheduleAtFixedRate(task, 1000L, 1000L); Thread.sleep(1000L * 2); timer.cancel(); }

4.3. TimerTask 내부 실행 의 스레드 중지

작업 의 run 메서드 내에서 스레드를 중지 하여 전체 작업을 취소 할 수도 있습니다 .

@Test public void givenUsingTimer_whenStoppingThread_thenTimerTaskIsCancelled() throws InterruptedException { TimerTask task = new TimerTask() { public void run() { System.out.println("Task performed on " + new Date()); // TODO: stop the thread here } }; Timer timer = new Timer("Timer"); timer.scheduleAtFixedRate(task, 1000L, 1000L); Thread.sleep(1000L * 2); }

실행 구현 에서 TODO 명령을 확인하십시오. 이 간단한 예제를 실행하려면 실제로 스레드를 중지해야합니다.

실제 사용자 정의 스레드 구현에서는 스레드 중지가 지원되어야하지만이 경우 사용 중단을 무시 하고 Thread 클래스 자체 에서 간단한 중지 API를 사용할 수 있습니다.

5. 타이머ExecutorService

타이머를 사용하는 대신 ExecutorService를 사용하여 타이머 작업을 예약 할 수도 있습니다.

다음은 지정된 간격으로 반복 작업을 실행하는 방법에 대한 간단한 예입니다.

@Test public void givenUsingExecutorService_whenSchedulingRepeatedTask_thenCorrect() throws InterruptedException { TimerTask repeatedTask = new TimerTask() { public void run() { System.out.println("Task performed on " + new Date()); } }; ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); long delay = 1000L; long period = 1000L; executor.scheduleAtFixedRate(repeatedTask, delay, period, TimeUnit.MILLISECONDS); Thread.sleep(delay + period * 3); executor.shutdown(); }

So what are the main differences between the Timer and the ExecutorService solution:

  • Timer can be sensitive to changes in the system clock; ScheduledThreadPoolExecutor is not
  • Timer has only one execution thread; ScheduledThreadPoolExecutor can be configured with any number of threads
  • Runtime Exceptions thrown inside the TimerTask kill the thread, so following scheduled tasks won't run further; with ScheduledThreadExecutor – the current task will be canceled, but the rest will continue to run

6. Conclusion

이 자습서에서는 작업을 빠르게 예약하기 위해 Java에 내장 된 간단하면서도 유연한 TimerTimerTask 인프라를 사용할 수있는 다양한 방법을 설명했습니다 . 물론 필요한 경우 Java 세계에는 훨씬 더 복잡하고 완전한 솔루션이 있습니다 (예 : Quartz 라이브러리).하지만 시작하기에 아주 좋은 곳입니다.

이 예제의 구현은 GitHub 프로젝트에서 찾을 수 있습니다.이 프로젝트는 Eclipse 기반 프로젝트이므로 그대로 가져 와서 실행하기 쉽습니다.