Java Timer

1. Timer - det grunnleggende

Timer og TimerTask er java util-klasser som brukes til å planlegge oppgaver i en bakgrunnstråd. Med noen få ord - TimerTask er oppgaven å utføre og Timer er planleggeren.

2. Planlegg en oppgave en gang

2.1. Etter en gitt forsinkelse

La oss starte med ganske enkelt kjører en enkelt oppgave ved hjelp av en Timer:

@Test offentlig ugyldighet gittUsingTimer_whenSchedulingTaskOnce_thenCorrect () {TimerTask task = new TimerTask () {public void run () {System.out.println ("Oppgave utført på:" + ny dato () + "n" + "Trådens navn:" + Thread.currentThread (). GetName ()); }}; Timer timer = new Timer ("Timer"); lang forsinkelse = 1000L; timer.plan (oppgave, forsinkelse); }

Nå, dette utfører oppgaven etter en viss forsinkelse, gitt som den andre parameteren for rute() metode. Vi ser i neste avsnitt hvordan du planlegger en oppgave til en gitt dato og et gitt tidspunkt.

Merk at hvis vi kjører dette er en JUnit-test, bør vi legge til en Thread.sleep (forsinkelse * 2) ring for å la timertråden kjøre oppgaven før Junit-testen slutter å kjøre.

2.2. På en gitt dato og tid

La oss nå se Timer # tidsplan (TimerTask, Date) metode, som tar en Dato i stedet for en lang som sin andre parameter, slik at vi kan planlegge oppgaven på et bestemt øyeblikk, i stedet for etter en forsinkelse.

Denne gangen, la oss forestille oss at vi har en gammel eldre database, og vi vil migrere dataene til en ny database med et bedre skjema.

Vi kunne lage et DatabaseMigrationTask klasse som skal håndtere migrasjonen:

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

For enkelhets skyld representerer vi de to databasene med a Liste av String. Enkelt sagt, vår migrasjon består av å sette dataene fra den første listen til den andre.

For å utføre denne migreringen på ønsket øyeblikk, vi må bruke den overbelastede versjonen av rute() metode:

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

Som vi kan se, gir vi migreringsoppgaven samt datoen for kjøring til rute() metode.

Deretter utføres migreringen på det tidspunktet som er angitt av twoSecondsLater:

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

Mens vi er før dette øyeblikket, skjer ikke migrasjonen.

3. Planlegg en gjentakbar oppgave

Nå som vi har dekket hvordan vi planlegger en enkelt utførelse av en oppgave, la oss se hvordan vi skal håndtere repeterbare oppgaver.

Nok en gang er det flere muligheter som tilbys av Timer klasse: Vi kan sette opp repetisjonen til å observere enten en fast forsinkelse eller en fast hastighet.

En fast forsinkelse betyr at kjøringen vil starte en periode etter det siste kjøringen startet, selv om den ble forsinket (derfor selv utsatt).

La oss si at vi ønsker å planlegge en oppgave hvert annet sekund, og at den første utførelsen tar ett sekund og den andre tar to, men er forsinket med ett sekund. Deretter begynte den tredje henrettelsen på femte sekund:

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

På den andre siden, en fast rente betyr at hver utførelse vil respektere den opprinnelige tidsplanen, uansett om en tidligere utførelse er forsinket.

La oss bruke vårt forrige eksempel, med en fast rente, og den andre oppgaven starter etter tre sekunder (på grunn av forsinkelsen). Men den tredje etter fire sekunder (respekterer den opprinnelige planen for en utførelse hvert annet sekund):

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

Disse to prinsippene blir dekket, la oss se hvordan vi bruker dem.

For å kunne bruke fast forsinkelsesplanlegging er det to overbelastninger av rute() metode, hver tar en ekstra parameter som angir periodisiteten i millisekunder.

Hvorfor to overbelastninger? Fordi det fremdeles er muligheten til å starte oppgaven på et bestemt tidspunkt eller etter en viss forsinkelse.

Når det gjelder fastprisplanlegging, har vi de to planAtFixedRate () metoder som også tar en periodisitet i millisekunder. Igjen har vi en metode for å starte oppgaven på en gitt dato og et klokkeslett, og en annen for å starte den etter en gitt forsinkelse.

Det er også verdt å nevne at hvis en oppgave tar mer tid enn utførelsesperioden, utsetter den hele kjeden av henrettelser enten vi bruker fast forsinkelse eller fast hastighet.

3.1. Med en fast forsinkelse

La oss forestille oss at vi vil implementere et nyhetsbrevsystem, og sende en e-post til våre følgere hver uke. I så fall virker en repeterende oppgave ideell.

Så la oss planlegge nyhetsbrevet hvert sekund, som i utgangspunktet er spamming, men ettersom sendingen er falsk, er vi klar!

La oss først designe en Nyhetsbrev Oppgave:

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

Hver gang den kjøres, vil oppgaven skrive ut den planlagte tiden, som vi samler ved hjelp av TimerTask # planningExecutionTime () metode.

Hva om vi vil planlegge denne oppgaven hvert sekund i fast forsinkelsesmodus? Vi må bruke den overbelastede versjonen av rute() vi snakket om tidligere:

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

Selvfølgelig har vi bare testene i noen få tilfeller:

E-post sendt kl: 2020-01-01T10: 50: 30.860 E-post sendt kl: 2020-01-01T10: 50: 31.860 E-post sendt kl: 2020-01-01T10: 50: 32.861 E-post sendt kl: 2020-01-01T10: 50 : 33,861

Som vi kan se, er det minst ett sekund mellom hver utførelse, men de blir noen ganger forsinket med et millisekund. Dette fenomenet skyldes vår beslutning om å bruke repetisjoner med fast forsinkelse.

3.2. Med en fast rente

Nå, hva om vi skulle bruke gjentakelser med fast rente? Da må vi bruke planningAtFixedRate () metode:

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

Denne gangen, henrettelser blir ikke forsinket av de forrige:

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

3.3. Planlegg en daglig oppgave

Neste, la oss kjøre en oppgave en gang om dagen:

@Test public void givenUsingTimer_whenSchedulingDailyTask_thenCorrect () {TimerTask repeatTask = new TimerTask () {public void run () {System.out.println ("Oppgave utført på" + ny dato ()); }}; Timer timer = new Timer ("Timer"); lang forsinkelse = 1000L; lang periode = 1000L * 60L * 60L * 24L; timer.scheduleAtFixedRate (gjentatt oppgave, forsinkelse, periode); }

4. Avbryt Timer og TimerTask

En utførelse av en oppgave kan avbrytes på noen få måter:

4.1. Avbryt TimerTask Innsiden Løpe

Ved å ringe TimerTask.cancel () metoden inne i løpe() metodens implementering av TimerTask seg selv:

@Test offentlig tomrom givenUsingTimer_whenCancelingTimerTask_thenCorrect () kaster InterruptedException {TimerTask oppgave = ny TimerTask () {offentlig ugyldig kjøring () {System.out.println ("Oppgave utført på" + ny dato ()); Avbryt(); }}; Timer timer = new Timer ("Timer"); timer.scheduleAtFixedRate (oppgave, 1000L, 1000L); Tråd. Søvn (1000L * 2); }

4.2. Avbryt Timer

Ved å ringe Timer.cancel () metode på en Timer gjenstand:

@Test offentlig tomrom gittUsingTimer_whenCancelingTimer_thenCorrect () kaster InterruptedException {TimerTask oppgave = ny TimerTask () {offentlig ugyldig kjøring () {System.out.println ("Oppgave utført på" + ny dato ()); }}; Timer timer = new Timer ("Timer"); timer.scheduleAtFixedRate (oppgave, 1000L, 1000L); Tråd. Søvn (1000L * 2); timer.cancel (); }

4.3. Stopp tråden til TimerTask Innsiden Løpe

Du kan også stoppe tråden inne i løpe metode for oppgaven, og dermed avbryte hele oppgaven:

@Test offentlig ugyldighet gittUsingTimer_whenStoppingThread_thenTimerTaskIsCancelled () kaster InterruptedException {TimerTask oppgave = ny TimerTask () {offentlig ugyldig kjøring () {System.out.println ("Oppgave utført på" + ny dato ()); // TODO: stopp tråden her}}; Timer timer = new Timer ("Timer"); timer.scheduleAtFixedRate (oppgave, 1000L, 1000L); Tråd. Søvn (1000L * 2); }

Legg merke til TODO-instruksjonene i løpe implementering - for å kunne kjøre dette enkle eksemplet, må vi faktisk stoppe tråden.

I en virkelig tilpasset trådimplementering, bør stopping av tråden støttes, men i dette tilfellet kan vi ignorere avskrivningen og bruke det enkle Stoppe API på selve trådklassen.

5. Timer vs. ExecutorService

Du kan også bruke en ExecutorService til å planlegge timeroppgaver, i stedet for å bruke tidtakeren.

Her er et raskt eksempel på hvordan du kjører en gjentatt oppgave med et spesifisert intervall:

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

Så hva er de viktigste forskjellene mellom Timer og ExecutorService løsning:

  • Timer kan være følsom for endringer i systemklokken; ScheduledThreadPoolExecutor er ikke
  • Timer har bare en henrettelsestråd; ScheduledThreadPoolExecutor kan konfigureres med et hvilket som helst antall tråder
  • Runtime Unntak kastet inne i TimerTask drep tråden, så følgende planlagte oppgaver kjører ikke lenger; med PlanlagtThreadExecutor - den gjeldende oppgaven vil bli kansellert, men resten vil fortsette å kjøre

6. Konklusjon

Denne opplæringen illustrerte de mange måtene du kan bruke den enkle, men fleksible Timer og TimerTask infrastruktur innebygd i Java, for rask planlegging av oppgaver. Det er selvfølgelig mye mer komplekse og komplette løsninger i Java-verdenen hvis du trenger dem - for eksempel Quartz-biblioteket - men dette er et veldig bra sted å starte.

Implementeringen av disse eksemplene finnes i GitHub-prosjektet - dette er et formørkelsesbasert prosjekt, så det skal være enkelt å importere og kjøre som det er.