Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Leer Executors en Threadpool | High-Level Synchronisatiemechanismen
Multithreading in Java

bookExecutors en Threadpool

We hebben al een verscheidenheid aan mechanismen onderzocht voor het ondersteunen van multithreading, en Executors is daar één van!

Wat zijn Executors en Thread Pooling?

Executors is een mechanisme dat high-level abstracties biedt voor het beheren van threads. Hiermee kun je een threadpool aanmaken en beheren, die bestaat uit een set bestaande threads die klaar zijn om taken uit te voeren. In plaats van voor elke taak een nieuwe thread te maken, worden taken naar de pool gestuurd, waar hun uitvoering verdeeld wordt over de threads.

Wat is nu precies een threadpool? Het is een verzameling bestaande threads die klaar zijn om taken uit te voeren. Door gebruik te maken van een threadpool voorkom je de overhead van het herhaaldelijk aanmaken en vernietigen van threads, omdat dezelfde threads kunnen worden hergebruikt voor meerdere taken.

Note
Opmerking

Als er meer taken dan threads zijn, wachten de taken in de Task Queue. Een taak uit de wachtrij wordt afgehandeld door een beschikbare thread uit de pool, en zodra de taak is voltooid, pakt de thread een nieuwe taak uit de wachtrij. Zodra alle taken in de wachtrij zijn afgehandeld, blijven de threads actief en wachten op nieuwe taken.

Voorbeeld uit het dagelijks leven

Denk aan een restaurant waar koks (threads) bestellingen (taken) bereiden. In plaats van voor elke bestelling een nieuwe kok aan te nemen, heeft het restaurant een beperkt aantal koks in dienst die de bestellingen afhandelen zodra ze binnenkomen. Zodra een kok een bestelling heeft afgerond, neemt hij de volgende aan, wat helpt om de middelen van het restaurant efficiënt te benutten.

Main Method

newFixedThreadPool(int n): Maakt een pool met een vast aantal threads gelijk aan n.

Main.java

Main.java

copy
1
ExecutorService executorService = Executors.newFixedThreadPool(20);

newCachedThreadPool(): Maakt een pool die nieuwe threads kan aanmaken indien nodig, maar beschikbare threads hergebruikt indien aanwezig.

Main.java

Main.java

copy
1
ExecutorService executorService = Executors.newCachedThreadPool();

newSingleThreadExecutor(): Maakt een enkele threadpool aan die ervoor zorgt dat taken achtereenvolgens worden uitgevoerd, dus één na de ander. Dit is nuttig voor taken die in strikte volgorde moeten worden uitgevoerd.

Main.java

Main.java

copy
1
ExecutorService executorService = Executors.newSingleThreadExecutor();

In alle voorbeelden retourneren de methoden van Executors een implementatie van de ExecutorService-interface, die wordt gebruikt om threads te beheren.

ExecutorService biedt methoden voor het beheren van een pool van threads. Bijvoorbeeld, submit(Runnable task) accepteert een taak als een Runnable-object en plaatst deze in een wachtrij voor uitvoering. Het retourneert een Future-object, waarmee de status van de taak kan worden gecontroleerd en een resultaat kan worden verkregen als de taak een resultaat oplevert.

Main.java

Main.java

copy
12345678910111213141516171819202122232425262728293031323334
package com.example; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class Main { public static void main(String[] args) { // Create a thread pool with 5 threads ExecutorService executor = Executors.newFixedThreadPool(5); // Define the task to be executed Runnable task = () -> { System.out.println("Task is running: " + Thread.currentThread().getName()); try { Thread.sleep(2000); // Simulate some work } catch (InterruptedException e) { Thread.currentThread().interrupt(); } System.out.println("Task completed: " + Thread.currentThread().getName()); }; // Submit the task for execution and get a `Future` Future<?> future = executor.submit(task); // Check if the task is done System.out.println("Is task done? " + future.isDone()); // You can use `future` to check the status of the task or wait for its completion // Example: future.get() - blocks until the task is completed (not used in this example) // Initiate an orderly shutdown of the executor service executor.shutdown(); } }

De methode shutdown() start een geleidelijke afsluiting van de threadpool. Er worden geen nieuwe taken meer geaccepteerd, maar de huidige taken worden nog afgerond. Nadat deze methode is aangeroepen, kan de pool niet meer herstart worden.

De methode awaitTermination(long timeout, TimeUnit unit) wacht tot alle taken in de pool zijn afgerond binnen het opgegeven tijdsbestek. Dit is een blokkerende wacht waarmee je kunt garanderen dat alle taken zijn voltooid voordat de pool wordt afgesloten.

Ook hebben we het belangrijkste interface nog niet genoemd dat helpt om de status van de thread bij te houden, namelijk het Future-interface. De submit()-methode van het ExecutorService-interface geeft een implementatie van het Future-interface terug.

Als je het resultaat wilt verkrijgen van de uitvoering van een thread, kun je de get()-methode gebruiken. Als de thread Runnable implementeert, retourneert de get()-methode niets, maar als het Callable<T> is, retourneert het een waarde van het type T.

Main.java

Main.java

copy
12345678910111213141516171819202122232425262728293031
package com.example; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class Main { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(3); // Callable task that returns a result Callable<String> task = () -> { Thread.sleep(1000); // Simulate some work return "Task result"; }; Future<String> future = executor.submit(task); try { // Get the result of the task String result = future.get(); System.out.println("Task completed with result: " + result); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } executor.shutdown(); } }

Je kunt ook de cancel(boolean mayInterruptIfRunning) methode gebruiken om te proberen de uitvoering van een taak te annuleren. Als de taak nog niet is gestart, wordt deze geannuleerd. Als de taak al wordt uitgevoerd, kan deze onderbroken worden op basis van de mayInterruptIfRunning-vlag.

true: Als de taak wordt uitgevoerd, wordt deze onderbroken door Thread.interrupt() aan te roepen op de uitvoerende thread. false: Als de taak wordt uitgevoerd, wordt deze niet onderbroken en heeft de annuleringspoging geen effect op de momenteel draaiende taak.

Wel en 2 methoden die intuïtief begrijpelijk zijn qua functionaliteit:

  • isCancelled(): Controleert of de taak is geannuleerd;
  • isDone(): Controleert of de taak is voltooid.

Voorbeeld van gebruik

Note
Opmerking

De grootte van de threadpool is afhankelijk van de aard van de uit te voeren taken. Gewoonlijk dient de grootte van de threadpool niet hard gecodeerd te worden; deze moet aanpasbaar zijn. De optimale grootte wordt bepaald door het monitoren van de doorvoer van de uitgevoerde taken.

Het is maximaal efficiënt om het aantal threads = processor cores te gebruiken. Dit is zichtbaar in de code met Runtime.getRuntime().availableProcessors().

Main.java

Main.java

copy
1
int availableProcessors = Runtime.getRuntime().availableProcessors();

Verschillen tussen Direct Threads Aanmaken en het Gebruik van ExecutorService

De belangrijkste verschillen tussen het direct aanmaken van threads en het gebruik van ExecutorService zijn gebruiksgemak en resourcebeheer. Handmatig aanmaken van threads vereist individueel beheer van elke thread, wat de code en administratie bemoeilijkt.

ExecutorService vereenvoudigt het beheer door gebruik te maken van een threadpool, waardoor het verwerken van taken eenvoudiger wordt. Bovendien kan handmatige threadcreatie leiden tot een hoog resourceverbruik, terwijl ExecutorService het mogelijk maakt de grootte van de threadpool aan te passen.

Was alles duidelijk?

Hoe kunnen we het verbeteren?

Bedankt voor je feedback!

Sectie 3. Hoofdstuk 6

Vraag AI

expand

Vraag AI

ChatGPT

Vraag wat u wilt of probeer een van de voorgestelde vragen om onze chat te starten.

Awesome!

Completion rate improved to 3.33

bookExecutors en Threadpool

Veeg om het menu te tonen

We hebben al een verscheidenheid aan mechanismen onderzocht voor het ondersteunen van multithreading, en Executors is daar één van!

Wat zijn Executors en Thread Pooling?

Executors is een mechanisme dat high-level abstracties biedt voor het beheren van threads. Hiermee kun je een threadpool aanmaken en beheren, die bestaat uit een set bestaande threads die klaar zijn om taken uit te voeren. In plaats van voor elke taak een nieuwe thread te maken, worden taken naar de pool gestuurd, waar hun uitvoering verdeeld wordt over de threads.

Wat is nu precies een threadpool? Het is een verzameling bestaande threads die klaar zijn om taken uit te voeren. Door gebruik te maken van een threadpool voorkom je de overhead van het herhaaldelijk aanmaken en vernietigen van threads, omdat dezelfde threads kunnen worden hergebruikt voor meerdere taken.

Note
Opmerking

Als er meer taken dan threads zijn, wachten de taken in de Task Queue. Een taak uit de wachtrij wordt afgehandeld door een beschikbare thread uit de pool, en zodra de taak is voltooid, pakt de thread een nieuwe taak uit de wachtrij. Zodra alle taken in de wachtrij zijn afgehandeld, blijven de threads actief en wachten op nieuwe taken.

Voorbeeld uit het dagelijks leven

Denk aan een restaurant waar koks (threads) bestellingen (taken) bereiden. In plaats van voor elke bestelling een nieuwe kok aan te nemen, heeft het restaurant een beperkt aantal koks in dienst die de bestellingen afhandelen zodra ze binnenkomen. Zodra een kok een bestelling heeft afgerond, neemt hij de volgende aan, wat helpt om de middelen van het restaurant efficiënt te benutten.

Main Method

newFixedThreadPool(int n): Maakt een pool met een vast aantal threads gelijk aan n.

Main.java

Main.java

copy
1
ExecutorService executorService = Executors.newFixedThreadPool(20);

newCachedThreadPool(): Maakt een pool die nieuwe threads kan aanmaken indien nodig, maar beschikbare threads hergebruikt indien aanwezig.

Main.java

Main.java

copy
1
ExecutorService executorService = Executors.newCachedThreadPool();

newSingleThreadExecutor(): Maakt een enkele threadpool aan die ervoor zorgt dat taken achtereenvolgens worden uitgevoerd, dus één na de ander. Dit is nuttig voor taken die in strikte volgorde moeten worden uitgevoerd.

Main.java

Main.java

copy
1
ExecutorService executorService = Executors.newSingleThreadExecutor();

In alle voorbeelden retourneren de methoden van Executors een implementatie van de ExecutorService-interface, die wordt gebruikt om threads te beheren.

ExecutorService biedt methoden voor het beheren van een pool van threads. Bijvoorbeeld, submit(Runnable task) accepteert een taak als een Runnable-object en plaatst deze in een wachtrij voor uitvoering. Het retourneert een Future-object, waarmee de status van de taak kan worden gecontroleerd en een resultaat kan worden verkregen als de taak een resultaat oplevert.

Main.java

Main.java

copy
12345678910111213141516171819202122232425262728293031323334
package com.example; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class Main { public static void main(String[] args) { // Create a thread pool with 5 threads ExecutorService executor = Executors.newFixedThreadPool(5); // Define the task to be executed Runnable task = () -> { System.out.println("Task is running: " + Thread.currentThread().getName()); try { Thread.sleep(2000); // Simulate some work } catch (InterruptedException e) { Thread.currentThread().interrupt(); } System.out.println("Task completed: " + Thread.currentThread().getName()); }; // Submit the task for execution and get a `Future` Future<?> future = executor.submit(task); // Check if the task is done System.out.println("Is task done? " + future.isDone()); // You can use `future` to check the status of the task or wait for its completion // Example: future.get() - blocks until the task is completed (not used in this example) // Initiate an orderly shutdown of the executor service executor.shutdown(); } }

De methode shutdown() start een geleidelijke afsluiting van de threadpool. Er worden geen nieuwe taken meer geaccepteerd, maar de huidige taken worden nog afgerond. Nadat deze methode is aangeroepen, kan de pool niet meer herstart worden.

De methode awaitTermination(long timeout, TimeUnit unit) wacht tot alle taken in de pool zijn afgerond binnen het opgegeven tijdsbestek. Dit is een blokkerende wacht waarmee je kunt garanderen dat alle taken zijn voltooid voordat de pool wordt afgesloten.

Ook hebben we het belangrijkste interface nog niet genoemd dat helpt om de status van de thread bij te houden, namelijk het Future-interface. De submit()-methode van het ExecutorService-interface geeft een implementatie van het Future-interface terug.

Als je het resultaat wilt verkrijgen van de uitvoering van een thread, kun je de get()-methode gebruiken. Als de thread Runnable implementeert, retourneert de get()-methode niets, maar als het Callable<T> is, retourneert het een waarde van het type T.

Main.java

Main.java

copy
12345678910111213141516171819202122232425262728293031
package com.example; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class Main { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(3); // Callable task that returns a result Callable<String> task = () -> { Thread.sleep(1000); // Simulate some work return "Task result"; }; Future<String> future = executor.submit(task); try { // Get the result of the task String result = future.get(); System.out.println("Task completed with result: " + result); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } executor.shutdown(); } }

Je kunt ook de cancel(boolean mayInterruptIfRunning) methode gebruiken om te proberen de uitvoering van een taak te annuleren. Als de taak nog niet is gestart, wordt deze geannuleerd. Als de taak al wordt uitgevoerd, kan deze onderbroken worden op basis van de mayInterruptIfRunning-vlag.

true: Als de taak wordt uitgevoerd, wordt deze onderbroken door Thread.interrupt() aan te roepen op de uitvoerende thread. false: Als de taak wordt uitgevoerd, wordt deze niet onderbroken en heeft de annuleringspoging geen effect op de momenteel draaiende taak.

Wel en 2 methoden die intuïtief begrijpelijk zijn qua functionaliteit:

  • isCancelled(): Controleert of de taak is geannuleerd;
  • isDone(): Controleert of de taak is voltooid.

Voorbeeld van gebruik

Note
Opmerking

De grootte van de threadpool is afhankelijk van de aard van de uit te voeren taken. Gewoonlijk dient de grootte van de threadpool niet hard gecodeerd te worden; deze moet aanpasbaar zijn. De optimale grootte wordt bepaald door het monitoren van de doorvoer van de uitgevoerde taken.

Het is maximaal efficiënt om het aantal threads = processor cores te gebruiken. Dit is zichtbaar in de code met Runtime.getRuntime().availableProcessors().

Main.java

Main.java

copy
1
int availableProcessors = Runtime.getRuntime().availableProcessors();

Verschillen tussen Direct Threads Aanmaken en het Gebruik van ExecutorService

De belangrijkste verschillen tussen het direct aanmaken van threads en het gebruik van ExecutorService zijn gebruiksgemak en resourcebeheer. Handmatig aanmaken van threads vereist individueel beheer van elke thread, wat de code en administratie bemoeilijkt.

ExecutorService vereenvoudigt het beheer door gebruik te maken van een threadpool, waardoor het verwerken van taken eenvoudiger wordt. Bovendien kan handmatige threadcreatie leiden tot een hoog resourceverbruik, terwijl ExecutorService het mogelijk maakt de grootte van de threadpool aan te passen.

Was alles duidelijk?

Hoe kunnen we het verbeteren?

Bedankt voor je feedback!

Sectie 3. Hoofdstuk 6
some-alt