Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Lære Executors og Trådpool | Højniveausynkroniseringsmekanismer
Multitrådning i Java

bookExecutors og Trådpool

Vi har allerede undersøgt en række mekanismer til understøttelse af multitrådning, og Executors er en af dem!

Hvad er Executors og Thread Pooling?

Executors er en mekanisme, der tilbyder høj-niveau abstraktioner til håndtering af tråde. Det gør det muligt at oprette og administrere en trådpool, som består af et sæt foruddefinerede tråde, der er klar til at udføre opgaver. I stedet for at oprette en ny tråd for hver opgave, sendes opgaverne til poolen, hvor deres udførelse fordeles mellem trådene.

Hvad er egentlig en trådpool? Det er en samling af foruddefinerede tråde, der er klar til at udføre opgaver. Ved at bruge en trådpool undgår du omkostningerne ved gentagen oprettelse og nedlæggelse af tråde, da de samme tråde kan genbruges til flere opgaver.

Note
Bemærk

Hvis der er flere opgaver end tråde, venter opgaverne i Task Queue. En opgave fra køen håndteres af en tilgængelig tråd fra poolen, og når opgaven er færdig, tager tråden en ny opgave fra køen. Når alle opgaver i køen er afsluttet, forbliver trådene aktive og venter på nye opgaver.

Eksempel fra virkeligheden

Forestil dig en restaurant, hvor kokke (tråde) tilbereder bestillinger (opgaver). I stedet for at ansætte en ny kok for hver bestilling, har restauranten et begrænset antal kokke, som håndterer bestillingerne, efterhånden som de kommer ind. Når en kok er færdig med en bestilling, tager vedkommende den næste, hvilket hjælper med at udnytte restaurantens ressourcer effektivt.

Main Method

newFixedThreadPool(int n): Opretter en pulje med et fast antal tråde svarende til n.

Main.java

Main.java

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

newCachedThreadPool(): Opretter en pulje, der kan oprette nye tråde efter behov, men genbruger tilgængelige tråde, hvis der er nogen.

Main.java

Main.java

copy
1
ExecutorService executorService = Executors.newCachedThreadPool();

newSingleThreadExecutor(): Opretter en single thread pool, der sikrer, at opgaver udføres sekventielt, det vil sige én efter én. Dette er nyttigt for opgaver, der skal udføres i en streng rækkefølge.

Main.java

Main.java

copy
1
ExecutorService executorService = Executors.newSingleThreadExecutor();

I alle eksemplerne returnerer metoderne fra Executors en implementering af ExecutorService-interfacet, som bruges til at administrere tråde.

ExecutorService tilbyder metoder til at administrere en tråd-pool. For eksempel accepterer submit(Runnable task) en opgave som et Runnable-objekt og placerer den i en kø til eksekvering. Den returnerer et Future-objekt, som kan bruges til at tjekke status for opgaven og hente et resultat, hvis opgaven producerer et resultat.

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(); } }

Metoden shutdown() påbegynder en kontrolleret nedlukning af tråd-poolen. Den accepterer ikke nye opgaver, men færdiggør de nuværende opgaver. Når denne metode er kaldt, kan poolen ikke genstartes.

Metoden awaitTermination(long timeout, TimeUnit unit) afventer, at alle opgaver i poolen afsluttes inden for den angivne tidsramme. Dette er en blokerende ventetid, der sikrer, at alle opgaver er afsluttet før poolen afsluttes.

Vi har heller ikke nævnt den vigtigste interface, der hjælper med at spore trådens tilstand, nemlig Future-interfacet. submit()-metoden fra ExecutorService-interfacet returnerer en implementering af Future-interfacet.

Hvis du ønsker at hente resultatet af trådens udførelse, kan du bruge get()-metoden. Hvis tråden implementerer Runnable, returnerer get()-metoden intet, men hvis det er Callable<T>, returnerer den en værdi af typen 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(); } }

Du kan også bruge metoden cancel(boolean mayInterruptIfRunning) til at forsøge at annullere udførelsen af en opgave. Hvis opgaven endnu ikke er startet, vil den blive annulleret. Hvis opgaven allerede kører, kan den blive afbrudt afhængigt af mayInterruptIfRunning-flaget.

true: Hvis opgaven kører, vil den blive afbrudt ved at kalde Thread.interrupt() på den udførende tråd. false: Hvis opgaven kører, vil den ikke blive afbrudt, og annulleringsforsøget vil ikke have nogen effekt på den aktuelt kørende opgave.

Samt 2 metoder, der intuitivt forklarer deres funktion:

  • isCancelled(): Kontrollerer om opgaven er blevet annulleret;
  • isDone(): Kontrollerer om opgaven er blevet fuldført.

Eksempel på brug

Note
Bemærk

Størrelsen på trådpuljen afhænger af karakteren af de opgaver, der udføres. Typisk bør størrelsen på trådpuljen ikke være hardkodet; i stedet bør den være tilpasselig. Den optimale størrelse bestemmes ved at overvåge gennemstrømningen af de udførte opgaver.

Det er maksimalt effektivt at bruge antallet af threads = processor cores. Dette kan ses i koden ved at bruge Runtime.getRuntime().availableProcessors().

Main.java

Main.java

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

Forskelle mellem direkte oprettelse af tråde og brug af ExecutorService

De væsentligste forskelle mellem at oprette tråde direkte og at bruge ExecutorService er bekvemmelighed og ressourcehåndtering. Manuel oprettelse af tråde kræver individuel håndtering af hver tråd, hvilket komplicerer koden og administrationen.

ExecutorService forenkler håndteringen ved at bruge en trådpulje, hvilket gør opgavehåndtering lettere. Derudover kan manuel oprettelse af tråde føre til højt ressourceforbrug, mens ExecutorService giver mulighed for at tilpasse størrelsen på trådpuljen.

Var alt klart?

Hvordan kan vi forbedre det?

Tak for dine kommentarer!

Sektion 3. Kapitel 6

Spørg AI

expand

Spørg AI

ChatGPT

Spørg om hvad som helst eller prøv et af de foreslåede spørgsmål for at starte vores chat

Awesome!

Completion rate improved to 3.33

bookExecutors og Trådpool

Stryg for at vise menuen

Vi har allerede undersøgt en række mekanismer til understøttelse af multitrådning, og Executors er en af dem!

Hvad er Executors og Thread Pooling?

Executors er en mekanisme, der tilbyder høj-niveau abstraktioner til håndtering af tråde. Det gør det muligt at oprette og administrere en trådpool, som består af et sæt foruddefinerede tråde, der er klar til at udføre opgaver. I stedet for at oprette en ny tråd for hver opgave, sendes opgaverne til poolen, hvor deres udførelse fordeles mellem trådene.

Hvad er egentlig en trådpool? Det er en samling af foruddefinerede tråde, der er klar til at udføre opgaver. Ved at bruge en trådpool undgår du omkostningerne ved gentagen oprettelse og nedlæggelse af tråde, da de samme tråde kan genbruges til flere opgaver.

Note
Bemærk

Hvis der er flere opgaver end tråde, venter opgaverne i Task Queue. En opgave fra køen håndteres af en tilgængelig tråd fra poolen, og når opgaven er færdig, tager tråden en ny opgave fra køen. Når alle opgaver i køen er afsluttet, forbliver trådene aktive og venter på nye opgaver.

Eksempel fra virkeligheden

Forestil dig en restaurant, hvor kokke (tråde) tilbereder bestillinger (opgaver). I stedet for at ansætte en ny kok for hver bestilling, har restauranten et begrænset antal kokke, som håndterer bestillingerne, efterhånden som de kommer ind. Når en kok er færdig med en bestilling, tager vedkommende den næste, hvilket hjælper med at udnytte restaurantens ressourcer effektivt.

Main Method

newFixedThreadPool(int n): Opretter en pulje med et fast antal tråde svarende til n.

Main.java

Main.java

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

newCachedThreadPool(): Opretter en pulje, der kan oprette nye tråde efter behov, men genbruger tilgængelige tråde, hvis der er nogen.

Main.java

Main.java

copy
1
ExecutorService executorService = Executors.newCachedThreadPool();

newSingleThreadExecutor(): Opretter en single thread pool, der sikrer, at opgaver udføres sekventielt, det vil sige én efter én. Dette er nyttigt for opgaver, der skal udføres i en streng rækkefølge.

Main.java

Main.java

copy
1
ExecutorService executorService = Executors.newSingleThreadExecutor();

I alle eksemplerne returnerer metoderne fra Executors en implementering af ExecutorService-interfacet, som bruges til at administrere tråde.

ExecutorService tilbyder metoder til at administrere en tråd-pool. For eksempel accepterer submit(Runnable task) en opgave som et Runnable-objekt og placerer den i en kø til eksekvering. Den returnerer et Future-objekt, som kan bruges til at tjekke status for opgaven og hente et resultat, hvis opgaven producerer et resultat.

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(); } }

Metoden shutdown() påbegynder en kontrolleret nedlukning af tråd-poolen. Den accepterer ikke nye opgaver, men færdiggør de nuværende opgaver. Når denne metode er kaldt, kan poolen ikke genstartes.

Metoden awaitTermination(long timeout, TimeUnit unit) afventer, at alle opgaver i poolen afsluttes inden for den angivne tidsramme. Dette er en blokerende ventetid, der sikrer, at alle opgaver er afsluttet før poolen afsluttes.

Vi har heller ikke nævnt den vigtigste interface, der hjælper med at spore trådens tilstand, nemlig Future-interfacet. submit()-metoden fra ExecutorService-interfacet returnerer en implementering af Future-interfacet.

Hvis du ønsker at hente resultatet af trådens udførelse, kan du bruge get()-metoden. Hvis tråden implementerer Runnable, returnerer get()-metoden intet, men hvis det er Callable<T>, returnerer den en værdi af typen 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(); } }

Du kan også bruge metoden cancel(boolean mayInterruptIfRunning) til at forsøge at annullere udførelsen af en opgave. Hvis opgaven endnu ikke er startet, vil den blive annulleret. Hvis opgaven allerede kører, kan den blive afbrudt afhængigt af mayInterruptIfRunning-flaget.

true: Hvis opgaven kører, vil den blive afbrudt ved at kalde Thread.interrupt() på den udførende tråd. false: Hvis opgaven kører, vil den ikke blive afbrudt, og annulleringsforsøget vil ikke have nogen effekt på den aktuelt kørende opgave.

Samt 2 metoder, der intuitivt forklarer deres funktion:

  • isCancelled(): Kontrollerer om opgaven er blevet annulleret;
  • isDone(): Kontrollerer om opgaven er blevet fuldført.

Eksempel på brug

Note
Bemærk

Størrelsen på trådpuljen afhænger af karakteren af de opgaver, der udføres. Typisk bør størrelsen på trådpuljen ikke være hardkodet; i stedet bør den være tilpasselig. Den optimale størrelse bestemmes ved at overvåge gennemstrømningen af de udførte opgaver.

Det er maksimalt effektivt at bruge antallet af threads = processor cores. Dette kan ses i koden ved at bruge Runtime.getRuntime().availableProcessors().

Main.java

Main.java

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

Forskelle mellem direkte oprettelse af tråde og brug af ExecutorService

De væsentligste forskelle mellem at oprette tråde direkte og at bruge ExecutorService er bekvemmelighed og ressourcehåndtering. Manuel oprettelse af tråde kræver individuel håndtering af hver tråd, hvilket komplicerer koden og administrationen.

ExecutorService forenkler håndteringen ved at bruge en trådpulje, hvilket gør opgavehåndtering lettere. Derudover kan manuel oprettelse af tråde føre til højt ressourceforbrug, mens ExecutorService giver mulighed for at tilpasse størrelsen på trådpuljen.

Var alt klart?

Hvordan kan vi forbedre det?

Tak for dine kommentarer!

Sektion 3. Kapitel 6
some-alt