Timer Java

1. Timer - Dasar-dasar

Timer dan TimerTask adalah kelas util java yang digunakan untuk menjadwalkan tugas di thread latar belakang. Singkatnya - TimerTask adalah tugas yang harus dilakukan dan Timer adalah penjadwal .

2. Jadwalkan Tugas Sekali

2.1. Setelah Penundaan Diberikan

Mari kita mulai dengan hanya menjalankan satu tugas dengan bantuan 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); }

Sekarang, ini menjalankan tugas setelah penundaan tertentu , diberikan sebagai parameter kedua dari metode schedule () . Kita akan melihat di bagian selanjutnya bagaimana menjadwalkan tugas pada tanggal dan waktu tertentu.

Perhatikan bahwa jika kita menjalankan ini adalah pengujian JUnit, kita harus menambahkan panggilan Thread.sleep (delay * 2) untuk memungkinkan thread Timer menjalankan tugas sebelum pengujian Junit berhenti dijalankan.

2.2. Pada Tanggal dan Waktu Tertentu

Sekarang, mari kita lihat metode Timer # schedule (TimerTask, Date) , yang menggunakan Date alih-alih long sebagai parameter keduanya, memungkinkan kita menjadwalkan tugas pada saat tertentu, bukan setelah penundaan.

Kali ini, mari kita bayangkan kita memiliki database lama dan kita ingin memindahkan datanya ke database baru dengan skema yang lebih baik.

Kita bisa membuat kelas DatabaseMigrationTask yang akan menangani migrasi itu:

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); } }

Untuk kesederhanaan, kami merepresentasikan dua database dengan List of String . Sederhananya, migrasi kami terdiri dari meletakkan data dari daftar pertama ke daftar kedua.

Untuk melakukan migrasi ini pada saat yang diinginkan, kita harus menggunakan versi kelebihan beban dari jadwal () metode :

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);

Seperti yang bisa kita lihat, kita memberikan tugas migrasi serta tanggal eksekusi ke metode schedule () .

Kemudian, migrasi dijalankan pada waktu yang ditunjukkan oleh twoSecondsLater :

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

Sementara kita sebelum momen ini, migrasi tidak terjadi.

3. Jadwalkan Tugas yang Berulang

Sekarang kita telah membahas cara menjadwalkan eksekusi tunggal suatu tugas, mari kita lihat cara menangani tugas yang berulang.

Sekali lagi, ada beberapa kemungkinan yang ditawarkan oleh kelas Timer : Kita dapat mengatur pengulangan untuk mengamati penundaan tetap atau tarif tetap.

Penundaan tetap berarti bahwa eksekusi akan dimulai dalam jangka waktu setelah eksekusi terakhir dimulai, bahkan jika itu ditunda (karena itu ditunda sendiri) .

Katakanlah kita ingin menjadwalkan beberapa tugas setiap dua detik, dan eksekusi pertama membutuhkan satu detik dan yang kedua membutuhkan dua tetapi tertunda satu detik. Kemudian, eksekusi ketiga akan dimulai pada detik kelima:

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

Di sisi lain, kurs tetap berarti bahwa setiap eksekusi akan mengikuti jadwal awal, tidak peduli jika eksekusi sebelumnya ditunda .

Mari kita gunakan kembali contoh kita sebelumnya, dengan kurs tetap, tugas kedua akan dimulai setelah tiga detik (karena penundaan). Tapi, yang ketiga setelah empat detik (dengan memperhatikan jadwal awal satu eksekusi setiap dua detik):

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

Kedua prinsip ini telah dibahas, mari kita lihat cara menggunakannya.

Untuk menggunakan penjadwalan penundaan tetap, ada dua kelebihan metode schedule () , masing-masing mengambil parameter ekstra yang menyatakan periodisitas dalam milidetik.

Mengapa dua kelebihan beban? Karena masih ada kemungkinan untuk memulai tugas pada saat tertentu atau setelah penundaan tertentu.

Sedangkan untuk penjadwalan tarif tetap, kami memiliki dua metode scheduleAtFixedRate () yang juga mengambil periodisitas dalam milidetik. Sekali lagi, kita punya satu metode untuk memulai tugas pada tanggal dan waktu tertentu dan metode lain untuk memulainya setelah penundaan tertentu.

Perlu juga disebutkan bahwa, jika tugas membutuhkan lebih banyak waktu daripada periode untuk dieksekusi, ini akan menunda seluruh rangkaian eksekusi baik kita menggunakan penundaan tetap atau tarif tetap.

3.1. Dengan Penundaan Tetap

Sekarang, mari kita bayangkan kita ingin menerapkan sistem buletin, mengirim email ke pengikut kita setiap minggu. Dalam hal ini, tugas berulang tampaknya ideal.

Jadi, mari jadwalkan buletin setiap detik, yang pada dasarnya adalah spamming, tetapi karena pengirimannya palsu, kami siap melakukannya!

Mari pertama-tama mendesain NewsletterTask :

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

Setiap kali dijalankan, tugas akan mencetak waktu yang dijadwalkan, yang kita kumpulkan menggunakan metode TimerTask # scheduleExecutionTime () .

Lalu, bagaimana jika kita ingin menjadwalkan tugas ini setiap detik dalam mode penundaan tetap? Kita harus menggunakan versi schedule () yang kelebihan beban yang kita bicarakan sebelumnya:

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

Tentu saja, kami hanya melakukan tes untuk beberapa kejadian:

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

Seperti yang bisa kita lihat, setidaknya ada satu detik di antara setiap eksekusi, tetapi terkadang tertunda selama satu milidetik. Fenomena itu karena keputusan kami untuk menggunakan pengulangan penundaan tetap.

3.2. Dengan Tarif Tetap

Sekarang, bagaimana jika kita menggunakan pengulangan dengan suku bunga tetap? Kemudian kita harus menggunakan metode scheduleAtFixedRate () :

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

This time, executions are not delayed by the previous ones:

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. Schedule a Daily Task

Next, let's run a task once a day:

@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. Cancel Timer and TimerTask

An execution of a task can be canceled in a few ways:

4.1. Cancel the TimerTask Inside Run

By calling the TimerTask.cancel() method inside the run() method's implementation of the TimerTask itself:

@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. Cancel the Timer

By calling the Timer.cancel() method on a Timer object:

@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. Stop the Thread of the TimerTask Inside Run

You can also stop the thread inside the run method of the task, thus canceling the entire task:

@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); }

Notice the TODO instruction in the run implementation – in order to run this simple example, we'll need to actually stop the thread.

In a real-world custom thread implementation, stopping the thread should be supported, but in this case we can ignore the deprecation and use the simple stop API on the Thread class itself.

5. Timer vs ExecutorService

You can also make good use of an ExecutorService to schedule timer tasks, instead of using the timer.

Here's a quick example of how to run a repeated task at a specified interval:

@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

Tutorial ini mengilustrasikan banyak cara untuk menggunakan infrastruktur Timer dan TimerTask yang sederhana namun fleksibel yang dibangun ke dalam Java, untuk menjadwalkan tugas dengan cepat. Tentu saja ada solusi yang jauh lebih kompleks dan lengkap di dunia Java jika Anda membutuhkannya - seperti pustaka Quartz - tetapi ini adalah tempat yang sangat baik untuk memulai.

Penerapan contoh-contoh ini dapat ditemukan di proyek GitHub - ini adalah proyek berbasis Eclipse, jadi semestinya mudah untuk mengimpor dan menjalankannya apa adanya.