Java >> Java tutorial >  >> Java

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-

  1. Eksekutørgrænseflader - Der er tre grænseflader Executor, ExecutorService og ScheduledExecutorService, der definerer de tre eksekveringsobjekttyper.
  2. 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.
  3. 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.

Send metoder i ExecutorService
  • Fremtidig indsend (opkaldbar opgave) - Indsender en værdi-returnerende opgave til udførelse og returnerer en fremtid, der repræsenterer de afventende resultater af opgaven.
  • 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 indsend(Kørbar opgave, T-resultat) - 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.
Lukningsmetoder i ExecutorService

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-

  1. Samlet tråd eksisterer separat fra de Kørbare og Kaldbare opgaver, den udfører og bruges ofte til at udføre flere opgaver.
  2. 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.


Java tag