Java-timer

1. Timer - det grundlæggende

Timer og TimerTask er java util-klasser, der bruges til at planlægge opgaver i en baggrundstråd. Med få ord - TimerTask er opgaven at udføre og Timer er planlæggeren.

2. Planlæg en opgave en gang

2.1. Efter en given forsinkelse

Lad os starte med simpelthen kører en enkelt opgave ved hjælp af en Timer:

@Test offentlig ugyldighed givenUsingTimer_whenSchedulingTaskOnce_thenCorrect () {TimerTask task = new TimerTask () {public void run () {System.out.println ("Opgave udført på:" + ny dato () + "n" + "Trådens navn:" + Thread.currentThread (). GetName ()); }}; Timer timer = ny Timer ("Timer"); lang forsinkelse = 1000L; timer.schedule (opgave, forsinkelse); }

Nu, dette udfører opgaven efter en vis forsinkelse, givet som den anden parameter for tidsplan() metode. Vi ser i det næste afsnit, hvordan man planlægger en opgave på en given dato og et tidspunkt.

Bemærk, at hvis vi kører dette er en JUnit-test, skal vi tilføje en Tråd. Søvn (forsinkelse * 2) kald for at lade timertråden køre opgaven, før Junit-testen holder op med at udføre.

2.2. På en given dato og et tidspunkt

Lad os nu se Timer # tidsplan (TimerTask, dato) metode, der tager en Dato i stedet for en lang som sin anden parameter, så vi kan planlægge opgaven på et bestemt øjeblik snarere end efter en forsinkelse.

Lad os forestille os, at vi har en gammel ældre database, og vi vil migrere dens data til en ny database med et bedre skema.

Vi kunne oprette en DatabaseMigrationTask klasse, der håndterer migrationen:

offentlig klasse DatabaseMigrationTask udvider TimerTask {privat liste oldDatabase; privat liste newDatabase; public DatabaseMigrationTask (List oldDatabase, List newDatabase) {this.oldDatabase = oldDatabase; this.newDatabase = newDatabase; } @ Override public void run () {newDatabase.addAll (oldDatabase); }}

For enkelheds skyld repræsenterer vi de to databaser med a Liste af Snor. Kort sagt, vores migration består i at placere dataene fra den første liste i den anden.

For at udføre denne migrering i det ønskede øjeblik, vi bliver nødt til at bruge den overbelastede version af tidsplan() metode:

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

Som vi kan se, giver vi migreringsopgaven samt datoen for udførelse til tidsplan() metode.

Derefter udføres migrationen på det tidspunkt, der er angivet af twoSecondsLater:

mens (LocalDateTime.now (). isBefore (twoSecondsLater)) {assertThat (newDatabase) .isEmpty (); Tråd. Søvn (500); } assertThat (newDatabase) .containsExactlyElementsOf (oldDatabase);

Mens vi er før dette øjeblik, sker migrationen ikke.

3. Planlæg en gentagelsesopgave

Nu hvor vi har dækket, hvordan vi planlægger en enkelt udførelse af en opgave, lad os se, hvordan vi håndterer gentagelige opgaver.

Endnu en gang er der flere muligheder, der tilbydes af Timer klasse: Vi kan indstille gentagelsen til at observere enten en fast forsinkelse eller en fast sats.

En fast forsinkelse betyder, at udførelsen starter en periode efter det øjeblik sidste udførelse startede, selvom den blev forsinket (derfor selv forsinket).

Lad os sige, at vi vil planlægge en opgave hvert andet sekund, og at den første udførelse tager et sekund, og den anden tager to, men er forsinket med et sekund. Derefter ville den tredje henrettelse starte med det femte sekund:

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

På den anden side, en fast sats betyder, at hver udførelse respekterer den oprindelige tidsplan, uanset om en tidligere udførelse er forsinket.

Lad os genbruge vores tidligere eksempel med en fast sats, den anden opgave starter efter tre sekunder (på grund af forsinkelsen). Men den tredje efter fire sekunder (respekterer den indledende tidsplan for en udførelse hvert andet sekund):

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

Disse to principper er dækket, lad os se, hvordan man bruger dem.

For at kunne bruge fast forsinkelsesplanlægning er der to overbelastninger af tidsplan() metode, hvor hver tager en ekstra parameter, der angiver periodiciteten i millisekunder.

Hvorfor to overbelastninger? Fordi der stadig er mulighed for at starte opgaven på et bestemt tidspunkt eller efter en vis forsinkelse.

Med hensyn til fast rente planlægning har vi de to scheduleAtFixedRate () metoder, der også tager en periodicitet i millisekunder. Igen har vi en metode til at starte opgaven på en given dato og et tidspunkt og en anden til at starte den efter en given forsinkelse.

Det er også værd at nævne, at hvis en opgave tager mere tid end perioden at udføre, forsinker den hele kæden af ​​henrettelser, uanset om vi bruger fast forsinkelse eller fast hastighed.

3.1. Med en fast forsinkelse

Lad os forestille os, at vi vil implementere et nyhedsbrevssystem og sende en e-mail til vores tilhængere hver uge. I så fald virker en gentagende opgave ideel.

Så lad os planlægge nyhedsbrevet hvert sekund, som dybest set spammer, men da afsendelsen er falsk, er vi klar til at gå!

Lad os først designe en NewsletterTask:

public class NewsletterTask udvider TimerTask {@Override public void run () {System.out.println ("E-mail sendt på:" + LocalDateTime.ofInstant (Instant.ofEpochMilli (planlagtExecutionTime ()), ZoneId.systemDefault ())); }}

Hver gang den udføres, udskriver opgaven den planlagte tid, som vi samler ved hjælp af TimerTask # planningExecutionTime () metode.

Hvad så hvis vi vil planlægge denne opgave hvert sekund i fast forsinkelsestilstand? Vi bliver nødt til at bruge den overbelastede version af tidsplan() vi talte om tidligere:

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

Selvfølgelig udfører vi kun testene i nogle få tilfælde:

E-mail sendt den: 2020-01-01T10: 50: 30.860 E-mail sendt den: 2020-01-01T10: 50: 31.860 E-mail sendt den: 2020-01-01T10: 50: 32.861 E-mail sendt den: 2020-01-01T10: 50 : 33,861

Som vi kan se, er der mindst et sekund mellem hver udførelse, men de er undertiden forsinket med et millisekund. Dette fænomen skyldes vores beslutning om at bruge gentagne faste forsinkelser.

3.2. Med en fast rente

Hvad nu hvis vi bruger en gentagelse med fast rente? Så skulle vi bruge planningAtFixedRate () metode:

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

Denne gang, henrettelser er ikke forsinket af de foregående:

E-mail sendt den: 2020-01-01T10: 55: 03.805 E-mail sendt den: 2020-01-01T10: 55: 04.805 E-mail sendt den: 2020-01-01T10: 55: 05.805 E-mail sendt den: 2020-01-01T10: 55 : 06.805

3.3. Planlæg en daglig opgave

Lad os derefter køre en opgave en gang om dagen:

@Test offentlig ugyldighed givenUsingTimer_whenSchedulingDailyTask_thenCorrect () {TimerTask repeatTask = new TimerTask () {public void run () {System.out.println ("Opgave udført på" + ny dato ()); }}; Timer timer = ny Timer ("Timer"); lang forsinkelse = 1000L; lang periode = 1000L * 60L * 60L * 24L; timer.scheduleAtFixedRate (gentaget opgave, forsinkelse, periode); }

4. Annuller Timer og TimerTask

En udførelse af en opgave kan annulleres på få måder:

4.1. Annuller TimerTask Inde Løb

Ved at ringe til TimerTask.cancel () metode inde i løb() metodens implementering af TimerTask sig selv:

@Test offentlig ugyldighed givenUsingTimer_whenCancelingTimerTask_thenCorrect () kaster InterruptedException {TimerTask task = new TimerTask () {public void run () {System.out.println ("Opgave udført på" + ny dato ()); afbestille(); }}; Timer timer = ny Timer ("Timer"); timer.scheduleAtFixedRate (opgave, 1000L, 1000L); Tråd. Søvn (1000L * 2); }

4.2. Annuller Timer

Ved at ringe til Timer.cancel () metode på en Timer objekt:

@Test offentlig ugyldighed givenUsingTimer_whenCancelingTimer_thenCorrect () kaster InterruptedException {TimerTask task = new TimerTask () {public void run () {System.out.println ("Opgave udført på" + ny dato ()); }}; Timer timer = ny Timer ("Timer"); timer.scheduleAtFixedRate (opgave, 1000L, 1000L); Tråd. Søvn (1000L * 2); timer.cancel (); }

4.3. Stop tråd af TimerTask Inde Løb

Du kan også stoppe tråden inde i løb metode til opgaven og annullerer således hele opgaven:

@Test offentlig ugyldighed givenUsingTimer_whenStoppingThread_thenTimerTaskIsCancelled () kaster InterruptedException {TimerTask task = new TimerTask () {public void run () {System.out.println ("Opgave udført på" + ny dato ()); // TODO: stop tråden her}}; Timer timer = ny Timer ("Timer"); timer.scheduleAtFixedRate (opgave, 1000L, 1000L); Tråd. Søvn (1000L * 2); }

Bemærk TODO-instruktionen i løb implementering - for at køre dette enkle eksempel skal vi faktisk stoppe tråden.

I en realtids tilpasset trådimplementering skal stoppe tråden understøttes, men i dette tilfælde kan vi ignorere afskrivningen og bruge det enkle hold op API på selve trådklassen.

5. Timer vs. ExecutorService

Du kan også bruge en ExecutorService til at planlægge timeropgaver i stedet for at bruge timeren.

Her er et hurtigt eksempel på, hvordan du kører en gentagen opgave med et bestemt interval:

@Test offentlig ugyldighed givenUsingExecutorService_whenSchedulingRepeatedTask_thenCorrect () kaster InterruptedException {TimerTask repeatTask = new TimerTask () {public void run () {System.out.println ("Opgave udført på" + ny dato ()); }}; ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor (); lang forsinkelse = 1000L; lang periode = 1000L; executor.scheduleAtFixedRate (repeatTask, delay, period, TimeUnit.MILLISECONDS); Thread.sleep (forsinkelse + periode * 3); executor.shutdown (); }

Så hvad er de største forskelle mellem Timer og ExecutorService opløsning:

  • Timer kan være følsomme over for ændringer i systemuret; ScheduledThreadPoolExecutor er ikke
  • Timer har kun en udførelsestråd; ScheduledThreadPoolExecutor kan konfigureres med et hvilket som helst antal tråde
  • Runtime undtagelser kastet inde i TimerTask dræb tråden, så følgende planlagte opgaver løber ikke længere; med ScheduledThreadExecutor - den aktuelle opgave annulleres, men resten fortsætter

6. Konklusion

Denne tutorial illustrerer de mange måder, du kan bruge den enkle, men alligevel fleksible Timer og TimerTask infrastruktur indbygget i Java til hurtig planlægning af opgaver. Der er selvfølgelig meget mere komplekse og komplette løsninger i Java-verdenen, hvis du har brug for dem - såsom Quartz-biblioteket - men dette er et meget godt sted at starte.

Implementeringen af ​​disse eksempler findes i GitHub-projektet - dette er et Eclipse-baseret projekt, så det skal være let at importere og køre som det er.


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