Java Executor Tutorial - Executor, ExecutorService, ScheduledExecutorService
I denne Java executor tutorial lærer du, hvordan du bruger Executor, ExecutorService, ScheduledExecutorService og deres thread pool implementeringer til effektivt at administrere tråde i en storstilet applikation.
Java Executor API
Mens du bruger Java multi-threading til at oprette tråd, er der en tæt forbindelse mellem opgaven, der udføres af en ny tråd, som defineret af dets Runnable-objekt, og selve tråden. Denne måde at administrere tråde på fungerer muligvis ikke godt med store applikationer. I store applikationer er det bedre at adskille trådoprettelse og trådstyring fra forretningslogikken . Java Executor framework hjælper med at gøre det ved at indkapsle trådoprettelse og styringsfunktionalitet i objekter kendt som eksekvere . Java Executor framework er kategoriseret i følgende tre dele-
- Eksekutørgrænseflader - Der er tre grænseflader Executor, ExecutorService og ScheduledExecutorService, der definerer de tre eksekveringsobjekttyper.
- Trådpuljer - Dette er eksekveringsimplementeringsklasserne som ThreadPoolExecutor og ScheduledThreadPoolExecutor, der udfører hver indsendt opgave ved hjælp af en af trådene fra trådpuljer.
- Fork/Join-ramme - Det er en implementering af ExecutorService-grænsefladen, der hjælper dig med at udnytte flere processorer.
Java Executor-grænseflade
Et objekt af typen Executor udfører indsendte Kørbare opgaver. Ved at bruge Executor behøver du ikke udtrykkeligt at oprette tråd.
For eksempel hvis der er et Kørbart objekt, der kan køres, kan du erstatte
(new Thread(runnable)).start();med
executor.execute(runnable);
hvor executor er et Executor-objekt.
Java Executor-grænsefladen har en enkelt execute-metode, som er defineret som følger-
void execute(Runnable command) - Udfører den givne runable på et tidspunkt i fremtiden. Et bestået kørselbart objekt kan udføres i en ny tråd, i en samlet tråd eller i den kaldende tråd, efter Executor-implementeringens skøn.
Java ExecutorService-grænseflade
ExecutorService-grænsefladen udvider Executor og tilføjer funktionalitet til nedlukning af eksekveren og funktionaliteten til at returnere en fremtid efter udførelse af en opgave.
Udover basismetoden execute (arvet fra Executor-grænsefladen), har ExecutorService mere alsidig submit()
metode, som er overbelastet til at acceptere Kørbare objekter samt Kaldbare objekter, som tillader opgaven at returnere en værdi.
Fremtidig - Indsender en værdi-returnerende opgave til udførelse og returnerer en fremtid, der repræsenterer de afventende resultater af opgaven.indsend (opkaldbar opgave) - Fremtidig> indsend(Kørbar opgave) - Sender en Runnable-opgave til udførelse og returnerer en Future, der repræsenterer denne opgave. Fremtidens get-metode vil returnere null efter vellykket afslutning.
Fremtidig - Sender en Runnable-opgave til udførelse og returnerer en Future, der repræsenterer denne opgave. Fremtidens get-metode vil returnere det givne resultat efter vellykket afslutning.indsend(Kørbar opgave, T-resultat)
Du kan lukke en ExecutorService ned, hvilket vil få den til at afvise nye opgaver.
- void shutdown() - Starter en velordnet nedlukning, hvor tidligere indsendte opgaver udføres, men ingen nye opgaver vil blive accepteret.
- List
shutdownNow() - Forsøg på at stoppe alle aktivt udførende opgaver, standser behandlingen af ventende opgaver og returnerer en liste over de opgaver, der ventede på at blive udført.
Java ScheduledExecutorService-grænseflade
ScheduledExecutorService-grænsefladen udvider ExecutorService-grænsefladen og tilføjer funktionalitet til at planlægge kommandoer, der skal køre efter en given forsinkelse, eller til at udføre periodisk.
Metoder til planlægning i ScheduledExecutorService-grænsefladen- plan (opkaldbar
opkaldbar, lang forsinkelse, TimeUnit-enhed) - Opretter og udfører en ScheduledFuture, der bliver aktiveret efter den givne forsinkelse. - plan (Kørbar kommando, lang forsinkelse, TimeUnit-enhed) - Opretter og udfører en one-shot handling, der bliver aktiveret efter den givne forsinkelse.
- scheduleAtFixedRate(Kørbar kommando, lang initialDelay, lang periode, TimeUnit-enhed) - Opretter og udfører en periodisk handling, der bliver aktiveret først efter den givne indledende forsinkelse, og efterfølgende med den givne periode.
- scheduleWithFixedDelay(Kørbar kommando, lang initialDelay, lang forsinkelse, TimeUnit-enhed) - Opretter og udfører en periodisk handling, der bliver aktiveret først efter den givne indledende forsinkelse, og efterfølgende med den givne forsinkelse mellem afslutningen af en udførelse og påbegyndelsen af den næste.
Java Executor implementeringsklasser
Nu ved vi om executor-grænsefladerne og de metoder, der er defineret i disse grænseflader. Java Executor framework har også foruddefinerede eksekveringsklasser, der implementerer disse grænseflader.
- ThreadPoolExecutor - Denne klasse implementerer Executor- og ExecutorService-grænseflader. ThreadPoolExecutor udfører hver indsendt opgave ved hjælp af en af muligvis flere poolede tråde.
- ScheduledThreadPoolExecutor - Denne klasse udvider ThreadPoolExecutor og implementerer ScheduledExecutorService. ScheduledThreadPoolExecutor klasseskemakommandoer til at køre efter en given forsinkelse eller til at udføre periodisk.
- ForkJoinPool - Denne klasse er en implementering af Executor- og ExecutorService-grænseflader. ForkJoinPool-klassen bruges i Fork/Join-rammeværket til at køre ForkJoinTasks.
For at læse mere om ThreadPoolExecutor-klassen i Java henvises til dette indlæg- Java ThreadPoolExecutor - Thread Pool med ExecutorService
For at læse mere om ScheduledThreadPoolExecutor-klassen i Java henvises til dette indlæg- Java ScheduledThreadPoollingExecutor - ScheduledThreadPoollingExecutor Med ExecutorService
De fleste af executor-implementeringerne i java.util.concurrent bruger trådpuljer, som består af arbejdstråde. Fordele du får ved at bruge trådpulje er-
- Samlet tråd eksisterer separat fra de Kørbare og Kaldbare opgaver, den udfører og bruges ofte til at udføre flere opgaver.
- Trådobjekter bruger en betydelig mængde hukommelse. I en storstilet applikation, hvis hver opgave bruger sin egen tråd, skaber allokering og deallokering af mange trådobjekter en betydelig hukommelsesstyringsoverhead. Brug af poolede tråde minimerer overhead på grund af trådskabelse.
Oprettelse af eksekvere ved hjælp af Executors-klassen
Før du går ind på eksempler for Executor og ExecutorService, skal du kende til en klasse mere; Executors klasse i Java concurrent API.
I stedet for at oprette og bruge forekomster af ThreadPoolExecutor og ScheduledThreadPoolExecutor direkte, kan du bruge statiske fabriksmetoder leveret af Executors-klassen til at få en executor. Disse fabriksmetoder kan oprette og returnere en ExecutorService, ScheduledExecutorService opsat med almindeligt nyttige konfigurationsindstillinger.
Følgende er listen over de mest almindeligt anvendte fabriksmetoder-
- static ExecutorService newCachedThreadPool() - Opretter en trådpulje, der opretter nye tråde efter behov, men vil genbruge tidligere konstruerede tråde, når de er tilgængelige.
- static ExecutorService newFixedThreadPool(int nThreads) - Opretter en trådpulje, der genbruger et fast antal tråde, der opererer fra en delt ubegrænset kø. På ethvert tidspunkt vil højst nThreads-tråde være aktive behandlingsopgaver.
- static ExecutorService newSingleThreadExecutor() - Opretter en Executor, der bruger en enkelt arbejdstråd, der opererer fra en ubegrænset kø
- statisk ScheduledExecutorService newSingleThreadScheduledExecutor() - Opretter en enkelt-trådet eksekvering, der kan planlægge kommandoer til at køre efter en given forsinkelse eller til at udføre periodisk.
- statisk ScheduledExecutorService newScheduledThreadPool(int corePoolSize) - Opretter en trådpulje, der kan planlægge kommandoer til at køre efter en given forsinkelse, eller til at udføre periodisk.
Eksempel på Java ExecutorService
1- I dette eksempel oprettes en ExecutorService ved at bruge metoden newFixedThreadPool() fra Executors-klassen. Trådpuljen oprettes med 2 tråde, så disse 2 tråde vil blive brugt til at udføre indsendte opgaver.
public class ExecutorExp { public static void main(String[] args) { // creating executor with pool of 2 threads ExecutorService executor = Executors.newFixedThreadPool(2); // running 4 tasks using pool of 2 threads executor.execute(new Task()); executor.execute(new Task()); executor.execute(new Task()); executor.execute(new Task()); executor.shutdown(); } } class Task implements Runnable{ @Override public void run() { System.out.println("Executing task (Thread name)- " + Thread.currentThread().getName()); try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }Output
Executing task (Thread name)- pool-1-thread-2 Executing task (Thread name)- pool-1-thread-1 Executing task (Thread name)- pool-1-thread-2 Executing task (Thread name)- pool-1-thread-1
Som du kan se udføres 4 opgaver ved hjælp af de 2 tråde fra puljen.
2- I dette eksempel på Java ExecutorService bruges afsendelsesmetoden for ExecutorService til at køre en opgave, der kan køres.
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; public class ExecutorExp { public static void main(String[] args) { // creating executor with pool of 2 threads ExecutorService executor = Executors.newFixedThreadPool(2); // running 4 tasks using pool of 2 threads Future<?> f1 = executor.submit(new Task()); Future<?> f2 = executor.submit(new Task()); Future<?> f3 = executor.submit(new Task()); Future<?> f4 = executor.submit(new Task()); try { System.out.println("f1- " + f1.get()); System.out.println("f2- " + f2.get()); if(f3.get() == null) { System.out.println("submitted task executed successfully"); } } catch (InterruptedException | ExecutionException e) { // TODO Auto-generated catch block e.printStackTrace(); } executor.shutdown(); } } class Task implements Runnable{ @Override public void run() { System.out.println("Executing task (Thread name)- " + Thread.currentThread().getName()); try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }Output
Executing task (Thread name)- pool-1-thread-2 Executing task (Thread name)- pool-1-thread-1 Executing task (Thread name)- pool-1-thread-2 Executing task (Thread name)- pool-1-thread-1 f1- null f2- null submitted task executed successfully
Som du kan se for en kørselsbar opgave, returnerer Futures get()-metode null efter vellykket gennemførelse af opgaven.
3- I dette eksempel bruges submit-metoden for ExecutorService til at køre en callbar opgave. Der er 2 klasser, der implementerer Callable og submit-metoden bruges til at køre disse callable opgaver. Senere værdi returneret fra Callable vises.
public class ExecutorExp { public static void main(String[] args) { // creating executor with pool of 2 threads ExecutorService executor = Executors.newFixedThreadPool(2); // running 4 tasks using pool of 2 threads Future<String> f1 = executor.submit(new Task1()); Future<String> f2 = executor.submit(new Task1()); Future<String> f3 = executor.submit(new Task2()); Future<String> f4 = executor.submit(new Task2()); try { System.out.println("f1- " + f1.get()); System.out.println("f2- " + f2.get()); System.out.println("f3- " + f3.get()); System.out.println("f4- " + f4.get()); } catch (InterruptedException | ExecutionException e) { // TODO Auto-generated catch block e.printStackTrace(); } executor.shutdown(); } } class Task1 implements Callable<String>{ @Override public String call() throws Exception { System.out.println("Executing task (Thread name)- " + Thread.currentThread().getName()); try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } return "In Task1"; } } class Task2 implements Callable<String>{ @Override public String call() throws Exception { System.out.println("Executing task (Thread name)- " + Thread.currentThread().getName()); try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } return "In Task2"; } }Output
Executing task (Thread name)- pool-1-thread-1 Executing task (Thread name)- pool-1-thread-2 f1- In Task1 Executing task (Thread name)- pool-1-thread-1 f2- In Task1 Executing task (Thread name)- pool-1-thread-2 f3- In Task2 f4- In Task2
Eksempel på Java ScheduledExecutorService
I dette eksempel oprettes en ScheduledExecutorService ved hjælp af newScheduledThreadPool()
metode i klassen Executors. En kaldbar opgave er planlagt til at udføre efter en forsinkelse på 3 sekunder.
public class ExecutorExp { public static void main(String[] args) { ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(2); // Callable implementation Callable<String> c = ()->{ System.out.println("Executed at- " + new Date()); return "Executing task"; }; System.out.println("Time before execution started- " + new Date()); // scheduling tasks with callable as param to be // executed after a delay of 3 Secs ScheduledFuture<String> sf = scheduledExecutor.schedule(c, 3, TimeUnit.SECONDS); try { System.out.println("Value- " + sf.get()); } catch (InterruptedException | ExecutionException e) { // TODO Auto-generated catch block e.printStackTrace(); } scheduledExecutor.shutdown(); } }Output
Time before execution started- Fri Jan 04 10:25:14 IST 2019 Executed at- Fri Jan 04 10:25:17 IST 2019 Value- Executing task
Eksempel på nedlukning af ExecutorService
I de foregående eksempler blev shutdown()-metoden brugt til at afslutte eksekveren. Da shutdown()-metoden sikrer, at tidligere indsendte opgaver udføres før nedlukningen, så der var ikke noget problem. Men der er også en shutdownNow()-metode, som ikke venter på, at aktivt udførende opgaver afsluttes. Lad os se det med et eksempel.
public class ExecutorExp { public static void main(String[] args) { // creating executor with pool of 2 threads ExecutorService executor = Executors.newFixedThreadPool(2); // running 4 tasks using pool of 2 threads Future<?> f1 = executor.submit(new Task()); Future<?> f2 = executor.submit(new Task()); Future<?> f3 = executor.submit(new Task()); Future<?> f4 = executor.submit(new Task()); System.out.println("shutting down instantly"); executor.shutdownNow(); } } class Task implements Runnable{ @Override public void run() { System.out.println("Executing task (Thread name)- " + Thread.currentThread().getName()); try { TimeUnit.MILLISECONDS.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }Output
java.lang.InterruptedException: sleep interrupted at java.base/java.lang.Thread.sleep(Native Method) at java.base/java.lang.Thread.sleep(Thread.java:340) at java.base/java.util.concurrent.TimeUnit.sleep(TimeUnit.java:403) at com.knpcode.Task.run(ExecutorExp.java:46) at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:514) at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1135) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) at java.base/java.lang.Thread.run(Thread.java:844) java.lang.InterruptedException: sleep interrupted at java.base/java.lang.Thread.sleep(Native Method) at java.base/java.lang.Thread.sleep(Thread.java:340) at java.base/java.util.concurrent.TimeUnit.sleep(TimeUnit.java:403) at com.knpcode.Task.run(ExecutorExp.java:46) at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:514) at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1135) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) at java.base/java.lang.Thread.run(Thread.java:844) shutting down instantly Executing task (Thread name)- pool-1-thread-1 Executing task (Thread name)- pool-1-thread-2
Som du kan se her, er nedlukningen øjeblikkelig. Da søvnmetoden blev kaldt på tråden, så den afbrydes for at lukke ned, er det derfor, InterruptedException kastes.
Anbefaling i henhold til Java-dokumenter er at lukke ExecutorService ned i to faser.
Først ved at kalde shutdown for at afvise indgående opgaver, og derefter kalde shutdownNow(), hvis det er nødvendigt, for at annullere eventuelle dvælende opgaver. shutdownNow() skal kaldes sammen med awaitTermination()-metoden for at give tid til, at den udførende opgave er færdig. Næste eksempel viser denne brug.
public class ExecutorExp { public static void main(String[] args) { // creating executor with pool of 2 threads ExecutorService executor = Executors.newFixedThreadPool(2); // running 4 tasks using pool of 2 threads Future<?> f1 = executor.submit(new Task()); Future<?> f2 = executor.submit(new Task()); Future<?> f3 = executor.submit(new Task()); Future<?> f4 = executor.submit(new Task()); System.out.println("shutting down instantly"); //executor.shutdownNow(); shutdownAndAwaitTermination(executor); } // For shutdown static void shutdownAndAwaitTermination(ExecutorService pool) { pool.shutdown(); // Disable new tasks from being submitted try { // Wait a while for existing tasks to terminate if (!pool.awaitTermination(1000, TimeUnit.MILLISECONDS)) { pool.shutdownNow(); // Cancel currently executing tasks // Wait a while for tasks to respond to being cancelled if (!pool.awaitTermination(500, TimeUnit.MILLISECONDS)) System.err.println("Pool did not terminate"); } } catch (InterruptedException ie) { // Cancel if current thread also interrupted pool.shutdownNow(); // Preserve interrupt status Thread.currentThread().interrupt(); } } } class Task implements Runnable{ @Override public void run() { System.out.println("Executing task (Thread name)- " + Thread.currentThread().getName()); try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
Det er alt for emnet Java Executor Tutorial - Executor, ExecutorService, ScheduledExecutorService . Hvis der mangler noget, eller du har noget at dele om emnet, så skriv en kommentar.