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øynivå Synkroniseringsmekanismer
Multitråding i Java

bookExecutors og Trådpool

Vi har allerede utforsket et utvalg av mekanismer for å støtte multitråding, og Executors er en av dem!

Hva er Executors og trådpooling?

Executors er en mekanisme som tilbyr høynivåabstraksjoner for håndtering av tråder. Den gjør det mulig å opprette og administrere en trådpool, som består av et sett med forhåndsopprettede tråder som er klare til å utføre oppgaver. I stedet for å opprette en ny tråd for hver oppgave, sendes oppgavene til poolen, hvor utførelsen fordeles mellom trådene.

Så, hva er egentlig en trådpool? Det er en samling av forhåndsopprettede tråder som er klare til å utføre oppgaver. Ved å bruke en trådpool unngår du belastningen ved å opprette og ødelegge tråder gjentatte ganger, siden de samme trådene kan gjenbrukes for flere oppgaver.

Note
Merk

Hvis det er flere oppgaver enn tråder, venter oppgavene i Task Queue. En oppgave fra køen håndteres av en tilgjengelig tråd fra poolen, og når oppgaven er fullført, plukker tråden opp en ny oppgave fra køen. Når alle oppgaver i køen er ferdige, forblir trådene aktive og venter på nye oppgaver.

Eksempel fra virkeligheten

Tenk på en restaurant der kokker (tråder) tilbereder bestillinger (oppgaver). I stedet for å ansette en ny kokk for hver bestilling, har restauranten et begrenset antall kokker som håndterer bestillinger etter hvert som de kommer inn. Når en kokk er ferdig med en bestilling, tar de neste, noe som bidrar til effektiv bruk av restaurantens ressurser.

Hovedmetode

newFixedThreadPool(int n): Oppretter en pulje med et fast antall tråder lik n.

Main.java

Main.java

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

newCachedThreadPool(): Oppretter en pulje som kan opprette nye tråder etter behov, men vil gjenbruke tilgjengelige tråder hvis det finnes noen.

Main.java

Main.java

copy
1
ExecutorService executorService = Executors.newCachedThreadPool();

newSingleThreadExecutor(): Oppretter en enkelttrådet trådpool som sikrer at oppgaver utføres sekvensielt, det vil si én etter én. Dette er nyttig for oppgaver som må utføres i streng rekkefølge.

Main.java

Main.java

copy
1
ExecutorService executorService = Executors.newSingleThreadExecutor();

I alle eksemplene returnerer metodene til Executors en implementasjon av ExecutorService-grensesnittet, som brukes til å administrere tråder.

ExecutorService tilbyr metoder for å administrere en trådpool. For eksempel aksepterer submit(Runnable task) en oppgave som et Runnable-objekt og plasserer den i en kø for utførelse. Den returnerer et Future-objekt, som kan brukes til å sjekke statusen til oppgaven og hente et resultat hvis oppgaven produserer 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() starter en kontrollert nedstengning av trådpoolen. Den slutter å akseptere nye oppgaver, men vil fullføre de nåværende oppgavene. Når du kaller denne metoden, kan ikke poolen startes på nytt.

Metoden awaitTermination(long timeout, TimeUnit unit) venter på at alle oppgaver i poolen skal fullføres innenfor den angitte tidsrammen. Dette er en blokkende venting som lar deg sikre at alle oppgaver er fullført før poolen avsluttes.

Vi har heller ikke nevnt det viktigste grensesnittet som hjelper til med å spore tilstanden til tråden, nemlig Future-grensesnittet. Metoden submit() i ExecutorService-grensesnittet returnerer en implementasjon av Future-grensesnittet.

Hvis du ønsker å hente resultatet av trådens utførelse, kan du bruke get()-metoden. Hvis tråden implementerer Runnable, returnerer get()-metoden ingenting, men hvis den implementerer Callable<T>, returnerer den en verdi av 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å bruke metoden cancel(boolean mayInterruptIfRunning) for å forsøke å avbryte utførelsen av en oppgave. Hvis oppgaven ikke har startet ennå, vil den bli avbrutt. Hvis oppgaven allerede kjører, kan den bli avbrutt basert på flagget mayInterruptIfRunning.

true: Hvis oppgaven kjører, vil den bli avbrutt ved å kalle Thread.interrupt() på den utførende tråden. false: Hvis oppgaven kjører, vil den ikke bli avbrutt, og forsøket på å avbryte vil ikke ha noen effekt på den nåværende kjørende oppgaven.

Vel og 2 metoder som intuitivt forklarer hva de gjør:

  • isCancelled(): Sjekker om oppgaven har blitt avbrutt;
  • isDone(): Sjekker om oppgaven har blitt fullført.

Eksempel på bruk

Note
Merk

Størrelsen på trådpoolen avhenger av typen oppgaver som kjøres. Vanligvis bør ikke størrelsen på trådpoolen være hardkodet; den bør heller være konfigurerbar. Optimal størrelse bestemmes ved å overvåke gjennomstrømningen til oppgavene som kjøres.

Det er mest effektivt å bruke antall threads = processor cores. Dette kan du se i koden ved å bruke Runtime.getRuntime().availableProcessors().

Main.java

Main.java

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

Forskjeller mellom å opprette tråder direkte og å bruke ExecutorService

De viktigste forskjellene mellom å opprette tråder direkte og å bruke ExecutorService er bekvemmelighet og ressursstyring. Manuell opprettelse av tråder krever at hver tråd administreres individuelt, noe som kompliserer koden og administrasjonen.

ExecutorService forenkler administrasjonen ved å bruke en trådpulje, noe som gjør håndteringen av oppgaver enklere. I tillegg kan manuell opprettelse av tråder føre til høyt ressursforbruk, mens ExecutorService lar deg tilpasse størrelsen på trådpuljen.

Alt var klart?

Hvordan kan vi forbedre det?

Takk for tilbakemeldingene dine!

Seksjon 3. Kapittel 6

Spør AI

expand

Spør AI

ChatGPT

Spør om hva du vil, eller prøv ett av de foreslåtte spørsmålene for å starte chatten vår

Awesome!

Completion rate improved to 3.33

bookExecutors og Trådpool

Sveip for å vise menyen

Vi har allerede utforsket et utvalg av mekanismer for å støtte multitråding, og Executors er en av dem!

Hva er Executors og trådpooling?

Executors er en mekanisme som tilbyr høynivåabstraksjoner for håndtering av tråder. Den gjør det mulig å opprette og administrere en trådpool, som består av et sett med forhåndsopprettede tråder som er klare til å utføre oppgaver. I stedet for å opprette en ny tråd for hver oppgave, sendes oppgavene til poolen, hvor utførelsen fordeles mellom trådene.

Så, hva er egentlig en trådpool? Det er en samling av forhåndsopprettede tråder som er klare til å utføre oppgaver. Ved å bruke en trådpool unngår du belastningen ved å opprette og ødelegge tråder gjentatte ganger, siden de samme trådene kan gjenbrukes for flere oppgaver.

Note
Merk

Hvis det er flere oppgaver enn tråder, venter oppgavene i Task Queue. En oppgave fra køen håndteres av en tilgjengelig tråd fra poolen, og når oppgaven er fullført, plukker tråden opp en ny oppgave fra køen. Når alle oppgaver i køen er ferdige, forblir trådene aktive og venter på nye oppgaver.

Eksempel fra virkeligheten

Tenk på en restaurant der kokker (tråder) tilbereder bestillinger (oppgaver). I stedet for å ansette en ny kokk for hver bestilling, har restauranten et begrenset antall kokker som håndterer bestillinger etter hvert som de kommer inn. Når en kokk er ferdig med en bestilling, tar de neste, noe som bidrar til effektiv bruk av restaurantens ressurser.

Hovedmetode

newFixedThreadPool(int n): Oppretter en pulje med et fast antall tråder lik n.

Main.java

Main.java

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

newCachedThreadPool(): Oppretter en pulje som kan opprette nye tråder etter behov, men vil gjenbruke tilgjengelige tråder hvis det finnes noen.

Main.java

Main.java

copy
1
ExecutorService executorService = Executors.newCachedThreadPool();

newSingleThreadExecutor(): Oppretter en enkelttrådet trådpool som sikrer at oppgaver utføres sekvensielt, det vil si én etter én. Dette er nyttig for oppgaver som må utføres i streng rekkefølge.

Main.java

Main.java

copy
1
ExecutorService executorService = Executors.newSingleThreadExecutor();

I alle eksemplene returnerer metodene til Executors en implementasjon av ExecutorService-grensesnittet, som brukes til å administrere tråder.

ExecutorService tilbyr metoder for å administrere en trådpool. For eksempel aksepterer submit(Runnable task) en oppgave som et Runnable-objekt og plasserer den i en kø for utførelse. Den returnerer et Future-objekt, som kan brukes til å sjekke statusen til oppgaven og hente et resultat hvis oppgaven produserer 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() starter en kontrollert nedstengning av trådpoolen. Den slutter å akseptere nye oppgaver, men vil fullføre de nåværende oppgavene. Når du kaller denne metoden, kan ikke poolen startes på nytt.

Metoden awaitTermination(long timeout, TimeUnit unit) venter på at alle oppgaver i poolen skal fullføres innenfor den angitte tidsrammen. Dette er en blokkende venting som lar deg sikre at alle oppgaver er fullført før poolen avsluttes.

Vi har heller ikke nevnt det viktigste grensesnittet som hjelper til med å spore tilstanden til tråden, nemlig Future-grensesnittet. Metoden submit() i ExecutorService-grensesnittet returnerer en implementasjon av Future-grensesnittet.

Hvis du ønsker å hente resultatet av trådens utførelse, kan du bruke get()-metoden. Hvis tråden implementerer Runnable, returnerer get()-metoden ingenting, men hvis den implementerer Callable<T>, returnerer den en verdi av 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å bruke metoden cancel(boolean mayInterruptIfRunning) for å forsøke å avbryte utførelsen av en oppgave. Hvis oppgaven ikke har startet ennå, vil den bli avbrutt. Hvis oppgaven allerede kjører, kan den bli avbrutt basert på flagget mayInterruptIfRunning.

true: Hvis oppgaven kjører, vil den bli avbrutt ved å kalle Thread.interrupt() på den utførende tråden. false: Hvis oppgaven kjører, vil den ikke bli avbrutt, og forsøket på å avbryte vil ikke ha noen effekt på den nåværende kjørende oppgaven.

Vel og 2 metoder som intuitivt forklarer hva de gjør:

  • isCancelled(): Sjekker om oppgaven har blitt avbrutt;
  • isDone(): Sjekker om oppgaven har blitt fullført.

Eksempel på bruk

Note
Merk

Størrelsen på trådpoolen avhenger av typen oppgaver som kjøres. Vanligvis bør ikke størrelsen på trådpoolen være hardkodet; den bør heller være konfigurerbar. Optimal størrelse bestemmes ved å overvåke gjennomstrømningen til oppgavene som kjøres.

Det er mest effektivt å bruke antall threads = processor cores. Dette kan du se i koden ved å bruke Runtime.getRuntime().availableProcessors().

Main.java

Main.java

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

Forskjeller mellom å opprette tråder direkte og å bruke ExecutorService

De viktigste forskjellene mellom å opprette tråder direkte og å bruke ExecutorService er bekvemmelighet og ressursstyring. Manuell opprettelse av tråder krever at hver tråd administreres individuelt, noe som kompliserer koden og administrasjonen.

ExecutorService forenkler administrasjonen ved å bruke en trådpulje, noe som gjør håndteringen av oppgaver enklere. I tillegg kan manuell opprettelse av tråder føre til høyt ressursforbruk, mens ExecutorService lar deg tilpasse størrelsen på trådpuljen.

Alt var klart?

Hvordan kan vi forbedre det?

Takk for tilbakemeldingene dine!

Seksjon 3. Kapittel 6
some-alt