Hoe u de uitvoering na een bepaalde tijd in Java kunt stoppen

1. Overzicht

In dit artikel leren we hoe we een langlopende uitvoering na een bepaalde tijd kunnen beëindigen. We zullen de verschillende oplossingen voor dit probleem onderzoeken. We zullen ook enkele van hun valkuilen behandelen.

2. Een lus gebruiken

Stel je voor dat we een heleboel items in een lus verwerken, zoals enkele details van de productitems in een e-commercetoepassing, maar dat het misschien niet nodig is om alle items te voltooien.

In feite willen we slechts tot een bepaalde tijd verwerken, en daarna willen we de uitvoering stoppen en laten zien wat de lijst tot dat moment heeft verwerkt.

Laten we een snel voorbeeld bekijken:

lange start = System.currentTimeMillis (); lange einde = start + 30 * 1000; while (System.currentTimeMillis () <end) {// Een dure bewerking van het item. }

Hier wordt de lus afgebroken als de tijd de limiet van 30 seconden heeft overschreden. Er zijn enkele opmerkelijke punten in de bovenstaande oplossing:

  • Lage nauwkeurigheid: De lus kan langer duren dan de opgelegde tijdslimiet. Dit hangt af van de tijd die elke iteratie kan duren. Als elke iteratie bijvoorbeeld tot 7 seconden kan duren, kan de totale tijd oplopen tot 35 seconden, wat ongeveer 17% langer is dan de gewenste tijdslimiet van 30 seconden.
  • Blokkeren: Een dergelijke verwerking in de hoofdthread is misschien geen goed idee, omdat deze deze lange tijd zal blokkeren. In plaats daarvan moeten deze bewerkingen worden losgekoppeld van de hoofdthread

In de volgende sectie zullen we bespreken hoe de op onderbreking gebaseerde benadering deze beperkingen elimineert.

3. Gebruik van een onderbrekingsmechanisme

Hier gebruiken we een aparte thread om de langlopende bewerkingen uit te voeren. De hoofdthread stuurt bij time-out een interruptsignaal naar de werkthread.

Als de werkdraad nog in leven is, zal deze het signaal opvangen en de uitvoering ervan stoppen. Als de werknemer klaar is vóór de time-out, heeft dit geen invloed op de werkthread.

Laten we de werkthread eens bekijken:

klasse LongRunningTask implementeert Runnable {@Override public void run () {probeer {while (! Thread.interrupted ()) {Thread.sleep (500); }} catch (InterruptedException e) {// log error}}}

Hier, Draad. Slaap simuleert een langdurige operatie. In plaats daarvan kan er een andere operatie plaatsvinden. Het is belangrijk om controleer de onderbrekingsvlag omdat niet alle bewerkingen onderbreekbaar zijn. Dus in die gevallen moeten we de vlag handmatig controleren.

We moeten deze vlag ook in elke iteratie controleren om ervoor te zorgen dat de thread stopt met zichzelf uitvoeren binnen de vertraging van maximaal één iteratie.

Vervolgens behandelen we drie verschillende mechanismen voor het verzenden van het interruptsignaal.

3.1. Gebruik maken van een Timer

Als alternatief kunnen we een TimerTask om de werkthread te onderbreken na een time-out:

klasse TimeOutTask breidt TimerTask {privé Thread t; privé Timer timer; TimeOutTask (Thread t, Timer timer) {this.t = t; this.timer = timer; } public void run () {if (t! = null && t.isAlive ()) {t.interrupt (); timer.cancel (); }}}

Hier hebben we een TimerTask dat neemt een werkdraad op het moment van zijn creatie. Het zal onderbreek de werkdraad bij het aanroepen van zijn rennen methode. De Timer zal het TimerTask na de gespecificeerde vertraging:

Thread t = new Thread (new LongRunningTask ()); Timer timer = nieuwe timer (); timer.schedule (nieuwe TimeOutTask (t, timer), 30 * 1000); t.start ();

3.2. Met behulp van de methode Future # get

We kunnen ook de krijgen methode van een Toekomst in plaats van een Timer:

ExecutorService executor = Executors.newSingleThreadExecutor (); Toekomstige toekomst = executor.submit (nieuwe LongRunningTask ()); probeer {f.get (30, TimeUnit.SECONDS); } catch (TimeoutException e) {f.cancel (true); } eindelijk {service.shutdownNow (); }

Hier hebben we de ExecutorService om de werkthread te verzenden die een exemplaar van Toekomst, van wie krijgen methode blokkeert de hoofdthread tot de opgegeven tijd. Het zal een TimeoutException na de opgegeven time-out. In de vangst block, onderbreken we de worker-thread door de annuleren methode op de F.uture voorwerp.

Het belangrijkste voordeel van deze aanpak ten opzichte van de vorige is dat het gebruikt een pool om de thread te beheren, terwijl de Timer gebruikt slechts een enkele thread (geen pool).

3.3. Gebruik maken van een GeplandeExcecutorSercvice

We kunnen ook gebruik maken van ScheduledExecutorService om de taak te onderbreken. Deze klasse is een uitbreiding van een ExecutorService en biedt dezelfde functionaliteit met de toevoeging van verschillende methoden die te maken hebben met het plannen van de uitvoering. Dit kan de gegeven taak uitvoeren na een bepaalde vertraging van de ingestelde tijdseenheden:

ScheduledExecutorService executor = Executors.newScheduledThreadPool (2); Toekomstige toekomst = executor.submit (nieuwe LongRunningTask ()); executor.schedule (new Runnable () {public void run () {future.cancel (true);}}, 1000, TimeUnit.MILLISECONDS); executor.shutdown ();

Hier hebben we met de methode een geplande threadpool van maat twee gemaakt newScheduledThreadPool. De ScheduledExecutorService #schema methode duurt een Runnable, een vertragingswaarde en de eenheid van de vertraging.

Het bovenstaande programma plant de uitvoering van de taak na één seconde vanaf het moment van indiening. Met deze taak wordt de oorspronkelijke langlopende taak geannuleerd.

Merk op dat we, in tegenstelling tot de vorige benadering, de hoofdthread niet blokkeren door de Future # get methode. Daarom het is de meest geprefereerde benadering van alle bovengenoemde benaderingen.

4. Is er garantie?

Er is geen garantie dat de uitvoering na een bepaalde tijd wordt gestopt. De belangrijkste reden is dat niet alle blokkeermethoden onderbreekbaar zijn. In feite zijn er maar een paar goed gedefinieerde methoden die onderbreekbaar zijn. Zo, als een thread wordt onderbroken en een vlag is ingesteld, gebeurt er niets anders totdat het een van deze onderbreekbare methoden bereikt.

Bijvoorbeeld, lezen en schrijven methoden zijn alleen onderbreekbaar als ze worden aangeroepen op streams die zijn gemaakt met een InterruptibleChannel. BufferedReader is geen InterruptibleChannel. Dus als de thread het gebruikt om een ​​bestand te lezen, roept onderbreken() op deze thread geblokkeerd in de lezen methode heeft geen effect.

We kunnen echter expliciet controleren op de interruptvlag na elke keer lezen in een lus. Dit geeft een redelijke zekerheid om de thread met enige vertraging te stoppen. Maar dit garandeert niet dat de thread na een strikte tijd wordt gestopt, omdat we niet weten hoeveel tijd een leesoperatie kan duren.

Aan de andere kant is het wacht methode van de Voorwerp klasse is onderbreekbaar. Zo is de draad geblokkeerd in de wacht methode zal onmiddellijk een InterruptedException nadat de onderbrekingsvlag is ingesteld.

We kunnen de blokkeermethoden identificeren door te zoeken naar een gooitInterruptedException in hun methodehandtekeningen.

Een belangrijk advies is om vermijd het gebruik van het verouderde Thread.stop () methode. Als u de thread stopt, ontgrendelt deze alle monitoren die hij heeft vergrendeld. Dit gebeurt vanwege de DraadDeath uitzondering die zich voortplant in de stapel.

Als een van de objecten die eerder door deze monitoren werden beschermd, in een inconsistente staat verkeerde, worden de inconsistente objecten zichtbaar voor andere threads. Dit kan leiden tot willekeurig gedrag dat zeer moeilijk te detecteren en te beredeneren is.

5. Conclusie

In deze tutorial hebben we verschillende technieken geleerd om de uitvoering na een bepaalde tijd te stoppen, samen met de voor- en nadelen van elk. De volledige broncode is te vinden op GitHub.