Pemasa Java

1. Pemasa - Asas

Timer dan TimerTask adalah kelas utiliti java yang digunakan untuk menjadualkan tugas dalam latar belakang. Dalam beberapa perkataan - TimerTask adalah tugas yang harus dilakukan dan Timer adalah penjadual .

2. Jadualkan Tugas Sekali

2.1. Selepas Kelewatan yang Diberikan

Mari mulakan dengan menjalankan satu tugas dengan bantuan Pemasa :

@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 melaksanakan tugas setelah penundaan tertentu , yang diberikan sebagai parameter kedua kaedah jadual () . Kami akan melihat di bahagian seterusnya bagaimana menjadualkan tugas pada tarikh dan waktu tertentu.

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

2.2. Pada Tarikh dan Masa yang Diberikan

Sekarang, mari kita lihat # jadual Pemasa (TimerTask, Tarikh) kaedah, yang mengambil Tarikh bukannya lama sebagai parameter kedua, membolehkan kita untuk menjadualkan tugas di satu ketika tertentu, dan bukannya selepas kelewatan.

Kali ini, mari kita bayangkan kami mempunyai pangkalan data lama dan kami ingin memindahkan datanya ke pangkalan data baru dengan skema yang lebih baik.

Kami dapat 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 memudahkan, kita mewakili dua pangkalan data oleh List of String . Ringkasnya, migrasi kami terdiri daripada memasukkan data dari senarai pertama ke senarai kedua.

Untuk melakukan migrasi ini pada masa yang diingini, kita harus menggunakan kaedah jadual () versi yang terlalu banyak :

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 dapat kita lihat, kita memberikan tugas migrasi dan juga tarikh pelaksanaan kepada kaedah jadual () .

Kemudian, migrasi dijalankan pada waktu yang ditunjukkan oleh twoSecondsLater :

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

Walaupun kita sebelum saat ini, penghijrahan tidak berlaku.

3. Jadualkan Tugas Berulang

Setelah kita membahas bagaimana menjadualkan pelaksanaan tugas tunggal, mari kita lihat bagaimana menangani tugas yang berulang.

Sekali lagi, terdapat banyak kemungkinan yang ditawarkan oleh kelas Pemasa : Kami dapat mengatur pengulangan untuk melihat sama ada kelewatan tetap atau kadar tetap.

Kelewatan tetap bermaksud bahawa pelaksanaan akan bermula dalam jangka waktu setelah pelaksanaan terakhir dimulakan, walaupun ia ditangguhkan (oleh itu ditangguhkan sendiri) .

Katakan kita mahu menjadualkan beberapa tugas setiap dua saat, dan bahawa pelaksanaan pertama memerlukan satu saat dan yang kedua memerlukan dua tetapi ditangguhkan satu saat. Kemudian, pelaksanaan ketiga akan bermula pada detik kelima:

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

Sebaliknya, kadar tetap bermaksud bahawa setiap pelaksanaan akan mematuhi jadual awal, tidak kira jika pelaksanaan sebelumnya ditangguhkan .

Mari gunakan semula contoh sebelumnya, dengan kadar tetap, tugas kedua akan bermula setelah tiga saat (kerana kelewatan). Tetapi, yang ketiga setelah empat saat (mematuhi jadual awal satu pelaksanaan setiap dua saat):

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

Kedua-dua prinsip ini diliputi, mari kita lihat bagaimana menggunakannya.

Untuk menggunakan penjadualan tetap-tertunda, terdapat dua lagi kaedah jadual () yang terlalu banyak , masing-masing mengambil parameter tambahan yang menyatakan berkala dalam milisaat.

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

Bagi penjadualan kadar tetap, kami mempunyai dua kaedah jadualAtFixedRate () yang juga mengambil berkala dalam milisaat. Sekali lagi, kami mempunyai satu kaedah untuk memulakan tugas pada tarikh dan waktu tertentu dan kaedah lain untuk memulakannya setelah kelewatan tertentu.

Perlu juga disebutkan bahawa, jika tugas memerlukan lebih banyak masa daripada jangka waktu pelaksanaannya, ia akan menunda keseluruhan rangkaian pelaksanaan sama ada kita menggunakan fixed-delay atau fixed-rate.

3.1. Dengan Kelewatan Tetap

Sekarang, bayangkan kita mahu menerapkan sistem buletin, menghantar e-mel kepada pengikut kita setiap minggu. Dalam kes itu, tugas berulang kelihatan ideal.

Oleh itu, mari menjadualkan buletin setiap saat, yang pada dasarnya adalah spam, tetapi kerana pengirimannya palsu, kita boleh pergi!

Mari pertama-tama merancang 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 dilaksanakan, tugas akan mencetak waktu yang dijadwalkan, yang kita kumpulkan menggunakan kaedah TimerTask # terjadwalExecutionTime () .

Lalu, bagaimana jika kita mahu menjadualkan tugas ini setiap saat dalam mod penangguhan tetap? Kita mesti menggunakan versi jadual yang terlalu banyak () yang telah kita bicarakan sebelumnya:

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

Sudah tentu, kami hanya menjalankan ujian 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 kita lihat, ada sekurang-kurangnya satu detik antara setiap pelaksanaan, tetapi kadang-kadang ditunda oleh satu milisaat. Fenomena itu disebabkan oleh keputusan kami untuk menggunakan pengulangan penangguhan tetap.

3.2. Dengan Kadar Tetap

Sekarang, bagaimana jika kita menggunakan pengulangan kadar tetap? Maka kita harus menggunakan kaedah terjadualAtFixedRate () :

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 menggambarkan banyak cara yang boleh anda gunakan untuk menggunakan infrastruktur Timer dan TimerTask yang ringkas namun fleksibel yang dibina di dalam Java, untuk menjadwalkan tugas dengan cepat. Sudah tentu ada penyelesaian yang lebih kompleks dan lengkap di dunia Java jika anda memerlukannya - seperti perpustakaan Quartz - tetapi ini adalah tempat yang sangat baik untuk memulakan.

Pelaksanaan contoh-contoh ini boleh didapati dalam projek GitHub - ini adalah projek berasaskan Eclipse, jadi mudah untuk diimport dan dijalankan sebagaimana adanya.