De Java ExecutorService interface, java.util.concurrent.ExecutorService, vertegenwoordigt een asynchroon uitvoeringsmechanisme dat in staat is om taken gelijktijdig op de achtergrond uit te voeren. In deze Java ExecutorService tutorial leg ik uit hoe je een ExecutorService maakt, hoe je er taken voor uitvoering aan voorlegt, hoe je de resultaten van die taken ziet, en hoe je de ExecutorService weer afsluit als je dat nodig hebt.

Java ExecutorService Video Tutorial

Als u de voorkeur geeft aan video, dan heb ik hier een video introductie:

Java ExecutorService Tutorial Video - Deel 1
Java ExecutorService Tutorial Video - Deel 2

Taak Delegatie

Hier ziet u een diagram dat een thread illustreert die een taak delegeert aan een Java ExecutorService voor asynchrone uitvoering:

Een thread die een taak delegeert aan een ExecutorService voor asynchrone uitvoering.

Een thread die een taak delegeert aan een ExecutorService voor asynchrone uitvoering.

Als de thread de taak eenmaal heeft gedelegeerd aan de ExecutorService, gaat de thread verder met zijn eigen uitvoering, onafhankelijk van de uitvoering van die taak. De ExecutorService voert de taak dan gelijktijdig uit, onafhankelijk van de thread die de taak heeft ingediend.

Java ExecutorService Voorbeeld

Voordat we te diep ingaan op de ExecutorService, laten we eerst naar een eenvoudig voorbeeld kijken. Hier is een eenvoudig Java ExecutorService voorbeeld:

ExecutorService executorService = Executors.newFixedThreadPool(10);executorService.execute(new Runnable() { public void run() { System.out.println("Asynchronous task"); }});executorService.shutdown();

Eerst wordt een ExecutorService aangemaakt met behulp van de ExecutorsnewFixedThreadPool() factory method. Hierdoor ontstaat een thread pool met 10 threads die taken uitvoeren.

Ten tweede wordt een anonieme implementatie van de Runnable interface doorgegeven aan de execute() methode. Dit zorgt ervoor dat de Runnable wordt uitgevoerd door een van de threads in de ExecutorService.

U zult in deze tutorial nog verschillende voorbeelden zien van het gebruik van de ExecutorService. Dit voorbeeld dient enkel om u een snel overzicht te geven van hoe het gebruik van een ExecutorService om taken op de achtergrond uit te voeren eruit ziet.

Java ExecutorService Implementations

De Java ExecutorService is zeer vergelijkbaar met een thread pool. In feite is de implementatie van de ExecutorService interface die in het java.util.concurrent pakket zit een thread pool implementatie. Als u wilt begrijpen hoe de ExecutorService interface intern kan worden geïmplementeerd, lees dan de bovenstaande tutorial.

Aangezien ExecutorService een interface is, hebt u de implementaties ervan nodig om er enig gebruik van te kunnen maken. De ExecutorService heeft de volgende implementatie in het java.util.concurrent pakket:

  • ThreadPoolExecutor
  • ScheduledThreadPoolExecutor

Een ExecutorService maken

Hoe u een ExecutorService maakt, hangt af van de implementatie die u gebruikt. U kunt echter de Executors factory class gebruiken om ook ExecutorService instanties te maken. Hier zijn een paar voorbeelden van het maken van een ExecutorService:

ExecutorService executorService1 = Executors.newSingleThreadExecutor();ExecutorService executorService2 = Executors.newFixedThreadPool(10);ExecutorService executorService3 = Executors.newScheduledThreadPool(10);

ExecutorService Usage

Er zijn een paar verschillende manieren om taken voor uitvoering te delegeren aan een ExecutorService:

  • uitvoeren(Runnable)
  • submit(Runnable)
  • submit(Callable)
  • invokeAny(…)
  • invokeAll(…)

Ik zal elk van deze methoden in de volgende secties bekijken.

Uitvoeren Runnable

De Java ExecutorServiceexecute(Runnable) methode neemt een java.lang.Runnable object, en voert het asynchroon uit. Hier is een voorbeeld van het uitvoeren van een Runnable met een ExecutorService:

ExecutorService executorService = Executors.newSingleThreadExecutor();executorService.execute(new Runnable() { public void run() { System.out.println("Asynchronous task"); }});executorService.shutdown();

Er is geen manier om het resultaat van de uitgevoerde Runnable te verkrijgen, indien nodig. U zult daarvoor een Callable moeten gebruiken (uitgelegd in de volgende secties).

Submit Runnable

De Java ExecutorServicesubmit(Runnable) methode neemt ook een Runnable implementatie, maar retourneert een Future object. Dit Future object kan worden gebruikt om te controleren of de Runnable klaar is met uitvoeren.

Hier volgt een Java ExecutorServicesubmit() voorbeeld:

Future future = executorService.submit(new Runnable() { public void run() { System.out.println("Asynchronous task"); }});future.get(); //returns null if the task has finished correctly.

De methode submit() retourneert een Java Future-object dat kan worden gebruikt om te controleren wanneer de Runnable is voltooid.

Submit Callable

De Java ExecutorServicesubmit(Callable) methode is vergelijkbaar met de submit(Runnable) methode behalve dat het een Java Callable neemt in plaats van een Runnable. Het precieze verschil tussen een Callable en een Runnable wordt verderop uitgelegd.

Het resultaat van de Callable kan worden verkregen via het Java Future object dat door de submit(Callable) methode wordt geretourneerd. Hier is een ExecutorServiceCallable voorbeeld:

Future future = executorService.submit(new Callable(){ public Object call() throws Exception { System.out.println("Asynchronous Callable"); return "Callable Result"; }});System.out.println("future.get() = " + future.get());

Het bovenstaande code-voorbeeld zal het volgende opleveren:

Asynchronous Callablefuture.get() = Callable Result

invokeAny()

De methode invokeAny() neemt een verzameling Callable-objecten, of subinterfaces van Callable. Het aanroepen van deze methode geeft geen Future terug, maar geeft het resultaat van een van de Callable objecten terug. U hebt geen garantie over welk van de Callable’s resultaten u krijgt. Gewoon een van degenen die afmaken.

Als een van de Callable’s afmaakt, zodat een resultaat wordt geretourneerd van invokeAny(), dan wordt de rest van de Callable instanties geannuleerd.

Als een van de taken afmaakt (of een exception gooit), dan wordt de rest van de Callable’s geannuleerd.

Hier is een code voorbeeld:

ExecutorService executorService = Executors.newSingleThreadExecutor();Set<Callable<String>> callables = new HashSet<Callable<String>>();callables.add(new Callable<String>() { public String call() throws Exception { return "Task 1"; }});callables.add(new Callable<String>() { public String call() throws Exception { return "Task 2"; }});callables.add(new Callable<String>() { public String call() throws Exception { return "Task 3"; }});String result = executorService.invokeAny(callables);System.out.println("result = " + result);executorService.shutdown();

Dit code voorbeeld drukt het object af dat door een van de Callable’s in de gegeven collectie wordt teruggegeven. Ik heb geprobeerd het een paar keer uit te voeren, en het resultaat verandert. Soms is het “Taak 1”, soms “Taak 2” etc.

invokeAll()

De methode invokeAll() roept alle Callable-objecten op die je aan hem doorgeeft in de verzameling die als parameter is doorgegeven. De invokeAll() retourneert een lijst van Future objecten via welke u de resultaten van de uitvoeringen van elke Callable kunt verkrijgen.

Houd in gedachten dat een taak kan eindigen als gevolg van een uitzondering, dus het kan zijn dat hij niet “geslaagd” is. Er is geen manier op een Future om het verschil te zien.

Hier is een code voorbeeld:

ExecutorService executorService = Executors.newSingleThreadExecutor();Set<Callable<String>> callables = new HashSet<Callable<String>>();callables.add(new Callable<String>() { public String call() throws Exception { return "Task 1"; }});callables.add(new Callable<String>() { public String call() throws Exception { return "Task 2"; }});callables.add(new Callable<String>() { public String call() throws Exception { return "Task 3"; }});List<Future<String>> futures = executorService.invokeAll(callables);for(Future<String> future : futures){ System.out.println("future.get = " + future.get());}executorService.shutdown();

Runnable vs. Callable

De Runnable interface lijkt erg op de Callable interface. De Runnable interface vertegenwoordigt een taak die gelijktijdig kan worden uitgevoerd door een thread of een ExecutorService. De Callable kan alleen worden uitgevoerd door een ExecutorService. Beide interfaces hebben slechts een enkele methode. Er is echter een klein verschil tussen de Callable en Runnable interface. Het verschil tussen de Runnable en Callable interface is beter zichtbaar als je de interface declaraties ziet.

Hier is eerst de Runnable interface declaratie:

public interface Runnable { public void run();}

En hier is de Callable interface declaratie:

public interface Callable{ public Object call() throws Exception;}

Het belangrijkste verschil tussen de Runnablerun() methode en de Callablecall() methode is dat de call() methode een Object kan teruggeven bij het aanroepen van de methode. Een ander verschil tussen call() en run() is dat call() een exceptie kan werpen, terwijl run() dat niet kan (behalve voor niet-gecontroleerde excepties – subklassen van RuntimeException).

Als u een taak aan een Java ExecutorService moet voorleggen en u hebt een resultaat van de taak nodig, dan moet u uw taak de Callable interface laten implementeren. Anders kan uw taak gewoon de Runnable interface implementeren.

Taak annuleren

U kunt een taak (Runnable of Callable) annuleren die is ingediend bij een Java ExecutorService door de cancel() methode op te roepen op de Future die wordt geretourneerd wanneer de taak is ingediend. Het annuleren van de taak is alleen mogelijk als de taak nog niet begonnen is met uitvoeren. Hier is een voorbeeld van het annuleren van een taak door de Future.cancel() methode aan te roepen:

future.cancel();

ExecutorService Shutdown

Als u klaar bent met het gebruik van de Java ExecutorService moet u deze afsluiten, zodat de threads niet blijven lopen. Als uw toepassing wordt gestart via een main() methode en uw hoofddraad verlaat uw toepassing, dan zal de toepassing blijven lopen als u een actieve ExexutorService in uw toepassing heeft. De actieve threads binnen deze ExecutorService voorkomen dat de JVM wordt afgesloten.

shutdown()

Om de threads binnen de ExecutorService te beëindigen, roept u de shutdown() methode ervan op. De ExecutorService zal niet onmiddellijk sluiten, maar het zal niet langer nieuwe taken accepteren, en zodra alle threads klaar zijn met de huidige taken, sluit de ExecutorService zich af. Alle taken die aan de ExecutorService zijn voorgelegd voordat shutdown() wordt aangeroepen, worden uitgevoerd. Hier is een voorbeeld van het uitvoeren van een Java ExecutorService shutdown:

executorService.shutdown();

shutdownNow()

Als u de ExecutorService onmiddellijk wilt afsluiten, kunt u de shutdownNow() methode aanroepen. Dit zal proberen om alle uitvoerende taken meteen te stoppen, en slaat alle ingediende maar niet verwerkte taken over. Er worden geen garanties gegeven over de uitvoerende taken. Misschien stoppen ze, misschien gaan ze door tot het einde. Het is een poging naar best vermogen. Hier is een voorbeeld van het aanroepen van ExecutorServiceshutdownNow:

executorService.shutdownNow();

awaitTermination()

De ExecutorServiceawaitTermination() methode zal de thread die het aanroept blokkeren totdat ofwel de ExecutorService zich volledig heeft afgesloten, of tot een bepaalde time out optreedt. De awaitTermination() methode wordt typisch aangeroepen na het aanroepen van shutdown() of shutdownNow(). Hier is een voorbeeld van het aanroepen van ExecutorServiceawaitTermination():

executorService.shutdown();executorService.awaitTermination(10_000L, TimeUnit.MILLISECONDS );

Geef een reactie

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *