Java timer

1. Tajmer - osnove

Tajmer i Zadatak odbrojavanja su java util klase koje se koriste za planiranje zadataka u pozadini niti. U nekoliko riječi - Zadatak odbrojavanja je zadatak koji treba izvršiti i Tajmer je raspored.

2. Jednom zakažite zadatak

2.1. Nakon određenog kašnjenja

Krenimo jednostavno izvođenje jednog zadatka uz pomoć a Tajmer:

@Test public void givenUsingTimer_whenSchedulingTaskOnce_thenCorrect () {TimerTask task = new TimerTask () {public void run () {System.out.println ("Zadatak izveden na:" + novi datum () + "n" + "Naziv teme:" + Thread.currentThread (). GetName ()); }}; Timer timer = novi timer ("Timer"); dugo kašnjenje = 1000L; timer.schedule (zadatak, odgoda); }

Sada, ovo izvršava zadatak nakon određenog kašnjenja, dano kao drugi parametar raspored() metoda. U sljedećem ćemo odjeljku vidjeti kako rasporediti zadatak na zadani datum i vrijeme.

Imajte na umu da ako radimo ovo je JUnit test, trebali bismo dodati a Thread.sleep (kašnjenje * 2) poziv da se timer-ovoj niti omogući pokretanje zadatka prije nego što se test Junit zaustavi u izvršenju.

2.2. U određenom datumu i vremenu

Sada, da vidimo Raspored brojača (TimerTask, Date) metoda koja traje a Datum umjesto a dugo kao drugi parametar, omogućujući nam da zadatak rasporedimo u određenom trenutku, a ne nakon kašnjenja.

Ovaj put, zamislimo da imamo staru bazu podataka i želimo migrirati njene podatke u novu bazu podataka s boljom shemom.

Mogli bismo stvoriti DatabaseMigrationTask klasa koja će se nositi s tom migracijom:

javna klasa DatabaseMigrationTask proširuje TimerTask {privatni popis oldDatabase; privatni popis newDatabase; javna DatabaseMigrationTask (Popis stare baze podataka, Popis nove baze podataka) {this.oldDatabase = oldDatabase; this.newDatabase = newDatabase; } @Override public void run () {newDatabase.addAll (oldDatabase); }}

Radi jednostavnosti predstavljamo dvije baze podataka pomoću Popis od Niz. Jednostavno rečeno, naša se migracija sastoji od stavljanja podataka s prvog popisa na drugi.

Da biste izvršili ovu migraciju u željenom trenutku, morat ćemo upotrijebiti preopterećenu verziju raspored() metoda:

Popis oldDatabase = Arrays.asList ("Harrison Ford", "Carrie Fisher", "Mark Hamill"); Popis newDatabase = novi ArrayList (); LocalDateTime twoSecondsLater = LocalDateTime.now (). PlusSeconds (2); Datum twoSecondsLaterAsDate = Date.from (twoSecondsLater.atZone (ZoneId.systemDefault ()). ToInstant ()); novi Timer (). raspored (novi DatabaseMigrationTask (oldDatabase, newDatabase), twoSecondsLaterAsDate);

Kao što vidimo, zadatak migracije, kao i datum izvršenja, dajemo na raspored() metoda.

Zatim se migracija izvršava u vrijeme označeno s twoSecondsLater:

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

Iako smo prije ovog trenutka, migracija se ne događa.

3. Zakažite ponovljivi zadatak

Sad kad smo pokrili kako rasporediti pojedinačno izvršavanje zadatka, pogledajmo kako se nositi s ponovljivim zadacima.

Još jednom, postoji više mogućnosti koje nudi Tajmer klasa: Ponavljanje možemo postaviti tako da promatramo ili fiksno kašnjenje ili fiksnu brzinu.

Fiksno kašnjenje znači da će izvršenje započeti vremenski period nakon trenutka kada je posljednje izvršenje započelo, čak i ako je odgođeno (stoga i samo odlaganje).

Recimo da želimo rasporediti neki zadatak svake dvije sekunde i da prvo izvršavanje traje jednu sekundu, a drugo dvije, ali da odgađa jednu sekundu. Tada bi treće izvršenje započelo u petoj sekundi:

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

S druge strane, fiksna stopa znači da će svako izvršenje poštivati ​​početni raspored, bez obzira je li prethodno izvršenje odgođeno.

Ponovno upotrijebimo naš prethodni primjer, s fiksnom stopom, drugi zadatak započet će nakon tri sekunde (zbog kašnjenja). Ali, treći nakon četiri sekunde (poštujući početni raspored jednog izvršenja svake dvije sekunde):

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

Ova dva načela koja su obrađena, možemo vidjeti kako ih koristiti.

Kako bi se koristilo zakazivanje s fiksnim kašnjenjem, postoje još dva preopterećenja raspored() metoda, pri čemu svaki uzima dodatni parametar koji navodi periodičnost u milisekundama.

Zašto dva preopterećenja? Jer još uvijek postoji mogućnost pokretanja zadatka u određenom trenutku ili nakon određenog kašnjenja.

Što se tiče rasporeda s fiksnom stopom, imamo ih dvoje scheduleAtFixedRate () metode koje uzimaju periodičnost u milisekundama. Opet, imamo jednu metodu za pokretanje zadatka u određeni datum i vrijeme, a drugu za pokretanje zadatka nakon određenog kašnjenja.

Također je vrijedno spomenuti da, ako je zadatku potrebno više vremena nego što je razdoblje za njegovo izvršenje, on odgađa cijeli lanac izvršavanja bez obzira koristimo li fiksno kašnjenje ili fiksnu stopu.

3.1. S fiksnim kašnjenjem

Zamislimo sada da želimo implementirati bilten, šaljući e-poštu svojim sljedbenicima svaki tjedan. U tom se slučaju ponavljajući zadatak čini idealnim.

Dakle, zakažimo bilten svake sekunde, koji je u osnovi neželjena pošta, ali kako je slanje lažno, spremni smo za početak!

Prvo dizajnirajmo a Zadatak biltena:

public class NewsletterTask proširuje TimerTask {@Override public void run () {System.out.println ("E-pošta poslana na:" + LocalDateTime.ofInstant (Instant.ofEpochMilli (rasporedExecutionTime ()), ZoneId.systemDefault ())); }}

Svaki put kad se izvrši, zadatak će ispisati zakazano vrijeme koje prikupimo pomoću TimerTask # zakazanoExecutionTime () metoda.

Onda, što ako taj zadatak želimo rasporediti svake sekunde u načinu s fiksnim kašnjenjem? Morat ćemo koristiti preopterećenu verziju raspored() o čemu smo ranije razgovarali:

novi timer (). raspored (novi NewsletterTask (), 0, 1000); for (int i = 0; i <3; i ++) {Thread.sleep (1000); }

Testove provodimo samo nekoliko puta:

E-pošta poslana u: 2020-01-01T10: 50: 30.860 E-pošta poslana u: 2020-01-01T10: 50: 31.860 E-pošta poslana u: 2020-01-01T10: 50: 32.861 E-pošta poslana u: 2020-01-01T10: 50 : 33.861

Kao što vidimo, između svake egzekucije postoji najmanje jedna sekunda, ali one se ponekad odgode i za milisekundu. Taj je fenomen posljedica naše odluke da upotrijebimo ponavljanje s fiksnim kašnjenjem.

3.2. Uz fiksnu stopu

Što bi bilo da se poslužimo ponavljanjem s fiksnom brzinom? Tada bismo morali koristiti rasporedAtFixedRate () metoda:

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

Ovaj put, izvršenja ne odgađaju prethodna:

E-pošta poslana u: 2020-01-01T10: 55: 03.805 E-pošta poslana u: 2020-01-01T10: 55: 04.805 E-pošta poslana u: 2020-01-01T10: 55: 05.805 E-pošta poslana u: 2020-01-01T10: 55 : 06.805

3.3. Zakažite dnevni zadatak

Dalje, idemo pokrenuti zadatak jednom dnevno:

@Test javna praznina givenUsingTimer_whenSchedulingDailyTask_thenCorrect () {TimerTask repeatTask = new TimerTask () {public void run () {System.out.println ("Zadatak izvršen" + novi datum ()); }}; Timer timer = novi timer ("Timer"); dugo kašnjenje = 1000L; dugo razdoblje = 1000L * 60L * 60L * 24L; timer.scheduleAtFixedRate (ponovljeni zadatak, kašnjenje, točka); }

4. Otkaži Tajmer i Zadatak odbrojavanja

Izvršenje zadatka može se otkazati na nekoliko načina:

4.1. Otkažite Zadatak odbrojavanja Iznutra Trčanje

Pozivanjem TimerTask.cancel () metoda unutar trčanje() provedba metode Zadatak odbrojavanja sebe:

@Test javna praznina givenUsingTimer_whenCancelingTimerTask_thenCorrect () baca InterruptedException {TimerTask task = new TimerTask () {public void run () {System.out.println ("Zadatak izveden na" + novi datum ()); otkazati(); }}; Timer timer = novi timer ("Timer"); timer.scheduleAtFixedRate (zadatak, 1000L, 1000L); Navoj.spavanje (1000L * 2); }

4.2. Otkažite Tajmer

Pozivanjem Timer.cancel () metoda na a Tajmer objekt:

@Test public void givenUsingTimer_whenCancelingTimer_thenCorrect () baca InterruptedException {TimerTask task = new TimerTask () {public void run () {System.out.println ("Zadatak izvršen" + novi datum ()); }}; Timer timer = novi timer ("Timer"); timer.scheduleAtFixedRate (zadatak, 1000L, 1000L); Navoj.spavanje (1000L * 2); timer.cancel (); }

4.3. Zaustavi nit Zadatak odbrojavanja Iznutra Trčanje

Također možete zaustaviti nit unutar trčanje metoda zadatka, čime se poništava cijeli zadatak:

@Test public void givenUsingTimer_whenStoppingThread_thenTimerTaskIsCancelled () baca InterruptedException {TimerTask task = new TimerTask () {public void run () {System.out.println ("Zadatak izveden na" + novi datum ()); // TODO: zaustavite nit ovdje}}; Timer timer = novi timer ("Timer"); timer.scheduleAtFixedRate (zadatak, 1000L, 1000L); Navoj.spavanje (1000L * 2); }

Primijetite upute TODO u trčanje implementacija - da bismo pokrenuli ovaj jednostavni primjer, trebamo zapravo zaustaviti nit.

U stvarnoj implementaciji prilagođenih niti treba podržati zaustavljanje niti, ali u ovom slučaju možemo zanemariti ukidanje i koristiti jednostavno Stop API na samoj klasi Thread.

5. Tajmer nasuprot ExecutorService

Također možete dobro iskoristiti ExecutorService za planiranje zadataka odbrojavanja, umjesto korištenja odbrojavanja.

Evo kratkog primjera kako pokrenuti ponovljeni zadatak u određenom intervalu:

@Test public void givenUsingExecutorService_whenSchedulingRepeatedTask_thenCorrect () baca InterruptedException {TimerTask repeatTask = new TimerTask () {public void run () {System.out.println ("Zadatak izvršen na" + novi datum ()); }}; ScheduledExecutorService izvršitelj = Izvršitelji.newSingleThreadScheduledExecutor (); dugo kašnjenje = 1000L; dugo razdoblje = 1000L; executor.scheduleAtFixedRate (ponovljenaTask, odgoda, razdoblje, TimeUnit.MILLISECONDS); Thread.sleep (kašnjenje + razdoblje * 3); egzekutor.šutdown (); }

Dakle, koje su glavne razlike između Tajmer i ExecutorService riješenje:

  • Tajmer može biti osjetljiv na promjene sistemskog sata; ScheduledThreadPoolExecutor nije
  • Tajmer ima samo jednu nit izvršenja; ScheduledThreadPoolExecutor može se konfigurirati s bilo kojim brojem niti
  • Iznimke u vrijeme izvođenja bačene unutar Zadatak odbrojavanja ubiti nit, tako da se slijedeći planirani zadaci neće izvoditi dalje; s ScheduledThreadExeecuter - trenutni će zadatak biti otkazan, ali ostatak će se nastaviti izvoditi

6. Zaključak

Ovaj je vodič ilustrirao mnoge načine na koje možete iskoristiti jednostavan, ali fleksibilan Tajmer i Zadatak odbrojavanja infrastruktura ugrađena u Javu za brzo raspoređivanje zadataka. Postoje, naravno, puno složenija i cjelovitija rješenja u svijetu Jave ako su vam potrebna - poput Quartz knjižnice - ali ovo je vrlo dobro mjesto za početak.

Implementacija ovih primjera može se naći u projektu GitHub - ovo je projekt zasnovan na Eclipseu, pa bi ga trebalo lako uvesti i pokrenuti kakav jest.