Java-timer

1. Timer - de basis

Timer en TimerTask zijn Java-util-klassen die worden gebruikt om taken in een achtergrondthread te plannen. In een paar woorden - TimerTask is de taak die moet worden uitgevoerd en Timer is de planner.

2. Plan een keer een taak

2.1. Na een bepaalde vertraging

Laten we beginnen met simpelweg een enkele taak uitvoeren met behulp van een Timer:

@Test openbare ongeldige gegevenUsingTimer_whenSchedulingTaskOnce_thenCorrect () {TimerTask-taak = nieuwe TimerTask () {openbare ongeldige run () {System.out.println ("Taak uitgevoerd op:" + nieuwe datum () + "n" + "Naam discussielijn:" + Thread.currentThread (). GetName ()); }}; Timer timer = nieuwe timer ("Timer"); lange vertraging = 1000L; timer.schedule (taak, vertraging); }

Nu, dit voert de taak uit na een zekere vertraging, gegeven als de tweede parameter van de schema() methode. We zullen in de volgende sectie zien hoe u een taak op een bepaalde datum en tijd plant.

Merk op dat als we draaien dit een JUnit-test is, we een Thread.sleep (vertraging * 2) oproep om de thread van de timer de taak uit te laten voeren voordat de Junit-test stopt met uitvoeren.

2.2. Op een bepaalde datum en tijd

Laten we nu eens kijken naar de Timer # schema (TimerTask, Datum) methode, waarvoor een Datum inplaats van een lang als tweede parameter, waardoor we de taak op een bepaald moment kunnen plannen in plaats van na een vertraging.

Laten we ons deze keer voorstellen dat we een oude, verouderde database hebben en dat we de gegevens ervan willen migreren naar een nieuwe database met een beter schema.

We zouden een DatabaseMigrationTask klasse die die migratie zal afhandelen:

openbare klasse DatabaseMigrationTask breidt TimerTask uit {privélijst oldDatabase; privélijst newDatabase; openbare DatabaseMigrationTask (List oldDatabase, List newDatabase) {this.oldDatabase = oldDatabase; this.newDatabase = newDatabase; } @Override public void run () {newDatabase.addAll (oldDatabase); }}

Voor de eenvoud stellen we de twee databases voor met een Lijst van Draad. Simpel gezegd, onze migratie bestaat uit het plaatsen van de gegevens van de eerste lijst in de tweede.

Om deze migratie op het gewenste moment uit te voeren, we zullen de overbelaste versie van het schema() methode:

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

Zoals we kunnen zien, geven we de migratietaak en de datum van uitvoering aan het schema() methode.

Vervolgens wordt de migratie uitgevoerd op het tijdstip dat wordt aangegeven door twoSecondsLater:

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

Terwijl we voor dit moment zijn, vindt de migratie niet plaats.

3. Plan een herhaalbare taak

Nu we hebben besproken hoe we de enkele uitvoering van een taak kunnen plannen, gaan we kijken hoe we met herhaalbare taken kunnen omgaan.

Nogmaals, er zijn meerdere mogelijkheden die worden geboden door de Timer klasse: We kunnen de herhaling zo instellen dat er een vaste vertraging of een vaste snelheid wordt waargenomen.

Een vaste vertraging betekent dat de uitvoering begint een periode na het moment waarop de laatste uitvoering begon, zelfs als deze vertraging was (en dus zelf vertraagd is).

Laten we zeggen dat we elke twee seconden een taak willen plannen, en dat de eerste uitvoering een seconde duurt en de tweede twee, maar een seconde vertraging heeft. Dan zou de derde uitvoering beginnen op de vijfde seconde:

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

Aan de andere kant, een vast tarief betekent dat elke uitvoering het oorspronkelijke schema respecteert, ongeacht of een eerdere uitvoering is vertraagd.

Laten we ons vorige voorbeeld hergebruiken, met een vast tarief, de tweede taak start na drie seconden (vanwege de vertraging). Maar de derde na vier seconden (met inachtneming van het oorspronkelijke schema van één uitvoering om de twee seconden):

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

Deze twee principes worden behandeld, laten we eens kijken hoe we ze kunnen gebruiken.

Om gebruik te maken van planning met vaste vertraging, zijn er nog twee overbelastingen van de schema() methode, elk met een extra parameter die de periodiciteit in milliseconden aangeeft.

Waarom twee overbelastingen? Omdat er nog steeds de mogelijkheid is om de taak op een bepaald moment of na een bepaalde vertraging te starten.

Wat betreft de planning met vaste tarieven, we hebben de twee scheduleAtFixedRate () methoden die ook een periodiciteit in milliseconden nemen. Nogmaals, we hebben één methode om de taak op een bepaalde datum en tijd te starten en een andere om deze na een bepaalde vertraging te starten.

Het is ook vermeldenswaard dat, als een taak meer tijd nodig heeft dan de periode om uit te voeren, dit de hele reeks van uitvoeringen vertraagt, of we nu een vaste vertraging of een vast tarief gebruiken.

3.1. Met een vaste vertraging

Laten we ons nu eens voorstellen dat we een nieuwsbriefsysteem willen implementeren, waarbij we elke week een e-mail naar onze volgers sturen. In dat geval lijkt een repetitieve taak ideaal.

Dus laten we de nieuwsbrief elke seconde plannen, wat in feite spam is, maar aangezien de verzending nep is, zijn we klaar om te gaan!

Laten we eerst een Nieuwsbrief:

openbare klasse NewsletterTask breidt TimerTask uit {@Override public void run () {System.out.println ("E-mail verzonden op:" + LocalDateTime.ofInstant (Instant.ofEpochMilli (geplandeExecutionTime ()), ZoneId.systemDefault ())); }}

Elke keer dat het wordt uitgevoerd, zal de taak de geplande tijd afdrukken, die we verzamelen met behulp van de TimerTask # geplandeExecutionTime () methode.

Wat als we deze taak dan elke seconde in de modus met vaste vertraging willen plannen? We zullen de overbelaste versie van moeten gebruiken schema() we spraken eerder over:

nieuwe Timer (). schema (nieuwe NewsletterTask (), 0, 1000); voor (int i = 0; i <3; i ++) {Thread.sleep (1000); }

Natuurlijk voeren we de tests slechts voor een paar gevallen uit:

E-mail verzonden op: 2020-01-01T10: 50: 30.860 E-mail verzonden op: 2020-01-01T10: 50: 31.860 E-mail verzonden op: 2020-01-01T10: 50: 32.861 E-mail verzonden op: 2020-01-01T10: 50 : 33.861

Zoals we kunnen zien, zit er minstens één seconde tussen elke uitvoering, maar deze worden soms met een milliseconde vertraagd. Dat fenomeen is te wijten aan onze beslissing om herhaling met vaste vertraging te gebruiken.

3.2. Met een vast tarief

Nu, wat als we een herhaling met vaste rente zouden gebruiken? Dan zouden we de geplandeAtFixedRate () methode:

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

Deze keer, executies worden niet vertraagd door de vorige:

E-mail verzonden op: 2020-01-01T10: 55: 03.805 E-mail verzonden op: 2020-01-01T10: 55: 04.805 E-mail verzonden op: 2020-01-01T10: 55: 05.805 E-mail verzonden op: 2020-01-01T10: 55 : 06.805

3.3. Plan een dagelijkse taak

Laten we het volgende doen voer één keer per dag een taak uit:

@Test openbare ongeldige gegevenUsingTimer_whenSchedulingDailyTask_thenCorrect () {TimerTask repeatTask = nieuwe TimerTask () {openbare ongeldige run () {System.out.println ("Taak uitgevoerd op" + nieuwe datum ()); }}; Timer timer = nieuwe timer ("Timer"); lange vertraging = 1000L; lange periode = 1000L * 60L * 60L * 24L; timer.scheduleAtFixedRate (herhaalde taak, vertraging, periode); }

4. Annuleren Timer en TimerTask

Een uitvoering van een taak kan op een aantal manieren worden geannuleerd:

4.1. Annuleer het TimerTask Binnen Rennen

Door het TimerTask.cancel () methode binnen de rennen() methode implementatie van de TimerTask zelf:

@Test openbare ongeldige gegevenUsingTimer_whenCancelingTimerTask_thenCorrect () gooit InterruptedException {TimerTask-taak = nieuwe TimerTask () {openbare ongeldige run () {System.out.println ("Taak uitgevoerd op" + nieuwe datum ()); annuleren(); }}; Timer timer = nieuwe timer ("Timer"); timer.scheduleAtFixedRate (taak, 1000L, 1000L); Thread.sleep (1000L * 2); }

4.2. Annuleer het Timer

Door het Timer.cancel () methode op een Timer voorwerp:

@Test openbare ongeldige gegevenUsingTimer_whenCancelingTimer_thenCorrect () gooit InterruptedException {TimerTask-taak = nieuwe TimerTask () {openbare ongeldige run () {System.out.println ("Taak uitgevoerd op" + nieuwe datum ()); }}; Timer timer = nieuwe timer ("Timer"); timer.scheduleAtFixedRate (taak, 1000L, 1000L); Thread.sleep (1000L * 2); timer.cancel (); }

4.3. Stop de draad van de TimerTask Binnen Rennen

U kunt ook de draad in de rennen methode van de taak, waardoor de hele taak wordt geannuleerd:

@Test openbare ongeldige gegevenUsingTimer_whenStoppingThread_thenTimerTaskIsCancelled () gooit InterruptedException {TimerTask-taak = nieuwe TimerTask () {openbare ongeldige run () {System.out.println ("Taak uitgevoerd op" + nieuwe datum ()); // TODO: stop de thread hier}}; Timer timer = nieuwe timer ("Timer"); timer.scheduleAtFixedRate (taak, 1000L, 1000L); Thread.sleep (1000L * 2); }

Let op de TODO-instructie in het rennen implementatie - om dit eenvoudige voorbeeld uit te voeren, moeten we de thread daadwerkelijk stoppen.

In een real-world custom thread implementatie, zou het stoppen van de thread ondersteund moeten worden, maar in dit geval kunnen we de deprecatie negeren en de simpele hou op API op de Thread-klasse zelf.

5. Timer vs ExecutorService

U kunt ook goed gebruik maken van een ExecutorService om timertaken in te plannen, in plaats van de timer te gebruiken.

Hier is een snel voorbeeld van hoe u een herhaalde taak met een opgegeven interval kunt uitvoeren:

@Test openbare ongeldige gegevenUsingExecutorService_whenSchedulingRepeatedTask_thenCorrect () gooit InterruptedException {TimerTask repeatTask = nieuwe TimerTask () {openbare ongeldige run () {System.out.println ("Taak uitgevoerd op" + nieuwe datum ()); }}; ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor (); lange vertraging = 1000L; lange periode = 1000L; executor.scheduleAtFixedRate (repeatTask, delay, period, TimeUnit.MILLISECONDS); Thread.sleep (vertraging + periode * 3); executor.shutdown (); }

Dus wat zijn de belangrijkste verschillen tussen de Timer en de ExecutorService oplossing:

  • Timer kan gevoelig zijn voor veranderingen in de systeemklok; ScheduledThreadPoolExecutor is niet
  • Timer heeft slechts één uitvoeringsthread; ScheduledThreadPoolExecutor kan worden geconfigureerd met een willekeurig aantal threads
  • Runtime-uitzonderingen die in het TimerTask dood de thread, dus de volgende geplande taken zullen niet verder worden uitgevoerd; met ScheduledThreadExecutor - de huidige taak wordt geannuleerd, maar de rest blijft doorlopen

6. Conclusie

Deze tutorial illustreerde de vele manieren waarop u gebruik kunt maken van de eenvoudige maar flexibele Timer en TimerTask infrastructuur ingebouwd in Java, voor het snel plannen van taken. Er zijn natuurlijk veel complexere en complete oplossingen in de Java-wereld als je ze nodig hebt - zoals de Quartz-bibliotheek - maar dit is een heel goede plek om te beginnen.

De implementatie van deze voorbeelden is te vinden in het GitHub-project - dit is een op Eclipse gebaseerd project, dus het moet gemakkelijk te importeren en uit te voeren zijn zoals het is.


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