Časovač Java

1. Časovač - základy

Časovač a TimerTask sú triedy Java util používané na plánovanie úloh v vlákne na pozadí. Pár slovami - TimerTask je úlohou vykonať a Časovač je plánovač.

2. Naplánujte si úlohu raz

2.1. Po danom oneskorení

Začnime jednoducho spustenie jednej úlohy pomocou a Časovač:

@Test public void givenUsingTimer_whenSchedulingTaskOnce_thenCorrect () {TimerTask task = new TimerTask () {public void run () {System.out.println ("Úloha vykonaná dňa:" + nový dátum () + "n" + "Názov vlákna:" + Thread.currentThread (). GetName ()); }}; Časovač časovača = nový časovač ("Časovač"); dlhé oneskorenie = 1000L; timer.schedule (task, delay); }

Teraz, toto vykoná úlohu po určitom oneskorení, uvedený ako druhý parameter harmonogram () metóda. V nasledujúcej časti uvidíme, ako naplánovať úlohu na daný dátum a čas.

Upozorňujeme, že ak bežíme, jedná sa o test JUnit, mali by sme pridať a Thread.sleep (oneskorenie * 2) volanie umožňujúce vláknu časovača spustiť úlohu skôr, ako sa test Junit zastaví.

2.2. V daný dátum a čas

Teraz sa pozrime na Časový rozvrh časovača (TimerTask, dátum) metóda, ktorá vyžaduje a Dátum namiesto a dlho ako druhý parameter, ktorý nám umožňuje naplánovať úlohu na určitý okamih, a nie po oneskorení.

Tentokrát si predstavme, že máme starú starú databázu a chceme migrovať jej údaje do novej databázy s lepšou schémou.

Mohli by sme vytvoriť DatabaseMigrationTask trieda, ktorá túto migráciu zvládne:

verejná trieda DatabaseMigrationTask rozširuje TimerTask {private List oldDatabase; súkromný zoznam newDatabase; public DatabaseMigrationTask (List oldDatabase, List newDatabase) {this.oldDatabase = oldDatabase; this.newDatabase = newDatabase; } @Override public void run () {newDatabase.addAll (oldDatabase); }}

Pre jednoduchosť reprezentujeme dve databázy znakom a Zoznam z String. Jednoducho povedané, naša migrácia spočíva v vložení údajov z prvého zoznamu do druhého.

Ak chcete vykonať túto migráciu v požadovanom okamihu, budeme musieť použiť preťaženú verziu harmonogram() metóda:

Zoznam oldDatabase = Arrays.asList ("Harrison Ford", "Carrie Fisher", "Mark Hamill"); Zoznam newDatabase = nový ArrayList (); LocalDateTime twoSecondsLater = LocalDateTime.now (). PlusSeconds (2); Dátum twoSecondsLaterAsDate = Date.from (twoSecondsLater.atZone (ZoneId.systemDefault ()). ToInstant ()); new Timer (). schedule (new DatabaseMigrationTask (oldDatabase, newDatabase), twoSecondsLaterAsDate);

Ako vidíme, úlohe migrácie a dátumu vykonania pridelíme harmonogram () metóda.

Potom sa migrácia vykoná v čase označenom symbolom twoSecondsLater:

while (LocalDateTime.now (). isBefore (twoSecondsLater)) {assertThat (newDatabase) .isEmpty (); Závit. Spánok (500); } assertThat (newDatabase) .containsExactlyElementsOf (oldDatabase);

Kým sme pred týmto okamihom, migrácia sa nevyskytuje.

3. Naplánujte si opakovateľnú úlohu

Teraz, keď sme sa zaoberali tým, ako naplánovať jednotlivé vykonanie úlohy, pozrime sa, ako zvládnuť opakovateľné úlohy.

Opäť existuje niekoľko možností, ktoré ponúka Časovač trieda: Môžeme nastaviť opakovanie tak, aby sledovalo buď pevné oneskorenie alebo pevnú rýchlosť.

Pevne dané oneskorenie znamená, že vykonanie sa začne po uplynutí času od okamihu, keď sa začalo posledné spustenie, aj keď bolo oneskorené (preto sa oneskorilo samo).

Povedzme, že chceme naplánovať nejakú úlohu každé dve sekundy a že prvé vykonanie trvá jednu sekundu a druhé trvá dve, ale oneskorí sa o jednu sekundu. Potom by sa tretia poprava začala piatu sekundu:

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

Na druhej strane, pevná sadzba znamená, že každá realizácia bude rešpektovať pôvodný rozvrh, bez ohľadu na to, či bola predchádzajúca realizácia oneskorená.

Zopakujme si náš predchádzajúci príklad, s pevnou rýchlosťou sa druhá úloha spustí po troch sekundách (kvôli oneskoreniu). Ale tretí po štyroch sekundách (rešpektujúc pôvodný rozvrh jedného vykonania každé dve sekundy):

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

Tieto dva princípy sú pokryté, uvidíme, ako ich využiť.

Aby bolo možné použiť plánovanie s pevným oneskorením, existujú ďalšie dve preťaženia harmonogram () metóda, pričom každý berie ďalší parameter s uvedením periodicity v milisekundách.

Prečo dve preťaženia? Pretože stále existuje možnosť začať s úlohou v určitom okamihu alebo po určitom oneskorení.

Pokiaľ ide o plánovanie s pevnou sadzbou, máme dve scheduleAtFixedRate () metódy tiež vyžadujúce periodicitu v milisekundách. Opäť máme jednu metódu na spustenie úlohy v daný dátum a čas a druhú na jej spustenie po danom oneskorení.

Je tiež potrebné spomenúť, že ak vykonanie úlohy trvá dlhšie ako obdobie, oneskorí to celý reťazec vykonaní, či už používame pevné oneskorenie alebo pevnú rýchlosť.

3.1. S pevným oneskorením

Teraz si predstavme, že chceme implementovať systém bulletinov, ktorý bude každý týždeň posielať e-maily našim sledujúcim. V takom prípade sa opakujúca úloha javí ako ideálna.

Poďme si teda naplánovať bulletin na každú sekundu, čo je v podstate spam, ale keďže odosielanie je falošné, môžeme vyraziť!

Najprv si navrhnime a NewsletterTask:

public class NewsletterTask extend TimerTask {@Override public void run () {System.out.println ("E-mail odoslaný na:" + LocalDateTime.ofInstant (Instant.ofEpochMilli (scheduledExecutionTime ()), ZoneId.systemDefault ())); }}

Zakaždým, keď sa úloha spustí, vytlačí jej naplánovaný čas, ktorý zhromaždíme pomocou TimerTask # scheduledExecutionTime () metóda.

Čo potom, keď chceme naplánovať túto úlohu každú sekundu v režime s pevným oneskorením? Budeme musieť použiť preťaženú verziu harmonogram () hovorili sme o tom skôr:

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

Testy samozrejme vykonávame iba pre niekoľko prípadov:

E-mail odoslaný: 2020-01-01T10: 50: 30,860 E-mail odoslaný: 2020-01-01T10: 50: 31,860 E-mail odoslaný: 2020-01-01T10: 50: 32,861 E-mail odoslaný: 2020-01-01T10: 50 : 33,861

Ako vidíme, medzi každým vykonaním je minimálne jedna sekunda, niekedy sú však oneskorené o milisekundu. Tento jav je spôsobený našim rozhodnutím použiť opakovanie s fixným oneskorením.

3.2. S pevnou sadzbou

Čo by sa stalo, keby sme použili opakovanie s pevnou sadzbou? Potom by sme museli použiť scheduledAtFixedRate () metóda:

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

Tentokrát, exekúcie sa neodkladajú predošlými:

E-mail odoslaný o: 2020-01-01T10: 55: 03.805 E-mail odoslaný: 2020-01-01T10: 55: 04.805 E-mail odoslaný o: 2020-01-01T10: 55: 05.805 E-mail odoslaný o: 2020-01-01T10: 55 : 06,805

3.3. Naplánujte si dennú úlohu

Ďalej, poďme spustiť úlohu raz denne:

@Test public void givenUsingTimer_whenSchedulingDailyTask_thenCorrect () {TimerTask repeatTask = nový TimerTask () {public void run () {System.out.println ("Úloha vykonaná dňa" + nový dátum ()); }}; Časovač časovača = nový časovač ("Časovač"); dlhé oneskorenie = 1000L; dlhé obdobie = 1000L * 60L * 60L * 24L; timer.scheduleAtFixedRate (repeatTask, delay, period); }

4. Zrušiť Časovač a TimerTask

Vykonanie úlohy je možné zrušiť niekoľkými spôsobmi:

4.1. Zrušiť TimerTask Vo vnútri Bež

Zavolaním na TimerTask.cancel () metóda vo vnútri run () implementácia metódy TimerTask sám:

@Test public void givenUsingTimer_whenCancelingTimerTask_thenCorrect () vyvolá InterruptedException {TimerTask task = new TimerTask () {public void run () {System.out.println ("Úloha vykonaná dňa" + nový dátum ()); Zrušiť(); }}; Časovač časovača = nový časovač ("Časovač"); timer.scheduleAtFixedRate (task, 1000L, 1000L); Thread.sleep (1000L * 2); }

4.2. Zrušiť Časovač

Zavolaním na Timer.cancel () metóda na a Časovač objekt:

@Test public void givenUsingTimer_whenCancelingTimer_thenCorrect () vyvolá InterruptedException {TimerTask task = new TimerTask () {public void run () {System.out.println ("Úloha vykonaná dňa" + nový dátum ()); }}; Časovač časovača = nový časovač ("Časovač"); timer.scheduleAtFixedRate (task, 1000L, 1000L); Thread.sleep (1000L * 2); timer.cancel (); }

4.3. Zastavte vlákno TimerTask Vo vnútri Bež

Môžete tiež zastaviť vlákno vo vnútri bežať spôsob úlohy, čím sa zruší celá úloha:

@Test public void givenUsingTimer_whenStoppingThread_thenTimerTaskIsCancelled () vyvolá InterruptedException {TimerTask task = new TimerTask () {public void run () {System.out.println ("Úloha vykonaná dňa" + nový dátum ()); // TODO: tu zastavte vlákno}}; Časovač časovača = nový časovač ("Časovač"); timer.scheduleAtFixedRate (task, 1000L, 1000L); Thread.sleep (1000L * 2); }

Všimnite si inštrukciu TODO v bežať implementácia - aby sme mohli spustiť tento jednoduchý príklad, budeme musieť vlákno skutočne zastaviť.

Pri implementácii vlastného vlákna v reálnom svete by malo byť podporované zastavenie vlákna, ale v tomto prípade môžeme ignorovať ukončenie podpory a použiť jednoduchý zastav API na samotnej triede Thread.

5. Časovač vs ExecutorService

Namiesto použitia časovača môžete tiež dobre využiť službu ExecutorService na naplánovanie úloh časovača.

Tu je rýchly príklad toho, ako spustiť opakovanú úlohu v stanovenom intervale:

@Test public void givenUsingExecutorService_whenSchedulingRepeatedTask_thenCorrect () vyvolá InterruptedException {TimerTask repeatTask = nový TimerTask () {public void run () {System.out.println ("Úloha vykonaná dňa" + nový dátum ()); }}; ScheduledExecutorService vykonávateľ = Executors.newSingleThreadScheduledExecutor (); dlhé oneskorenie = 1000L; dlhé obdobie = 1000L; Exekutor.scheduleAtFixedRate (repeatTask, delay, period, TimeUnit.MILLISECONDS); Thread.sleep (oneskorenie + obdobie * 3); executor.shutdown (); }

Aké sú teda hlavné rozdiely medzi Časovač a ExecutorService Riešenie:

  • Časovač môže byť citlivý na zmeny v systémových hodinách; ScheduledThreadPoolExecutor nie je
  • Časovač má iba jedno vykonávacie vlákno; ScheduledThreadPoolExecutor je možné nakonfigurovať s ľubovoľným počtom vlákien
  • Runtime výnimky vyvolané vo vnútri TimerTask zabiť vlákno, takže sledovanie naplánovaných úloh nebude pokračovať; s ScheduledThreadExecutor - aktuálna úloha bude zrušená, ale zvyšok bude pokračovať

6. Záver

Tento výukový program ilustroval mnoho spôsobov, ako môžete využiť jednoduché, ale zároveň flexibilné Časovač a TimerTask infraštruktúra zabudovaná do Javy na rýchle plánovanie úloh. Vo svete Java samozrejme existujú oveľa zložitejšie a kompletnejšie riešenia, ako napríklad knižnica Quartz, ale je to veľmi dobré miesto pre začiatok.

Implementáciu týchto príkladov možno nájsť v projekte GitHub - jedná sa o projekt založený na Eclipse, takže by malo byť ľahké ho importovať a spustiť tak, ako je.


$config[zx-auto] not found$config[zx-overlay] not found