Miért van szüksége az Executor felületre?

A Java 5 előtt az összes saját kódszál-kezelést be kellett írnia az alkalmazásba. Emellett létrehozva aúj témaAz objektum erőforrás-igényes művelet, és nincs értelme minden könnyű feladathoz új szálat létrehozni. És mivel ez a probléma abszolút minden többszálas alkalmazások fejlesztője számára ismerős, úgy döntöttek, hogy végrehajtják a Java-ban ezt a funkciót Executor keretrendszerként.

Mi a nagy ötlet? Egyszerű: ahelyett, hogy minden új feladathoz új szálat hoznánk létre, a szálak egyfajta „tárhelyen” vannak tárolva, és amikor új feladat érkezik, új létrehozása helyett egy meglévő szálat kérünk le.

Ennek a keretrendszernek a fő felületei az Executor , az ExecutorService és a ScheduledExecutorService , amelyek mindegyike kiterjeszti az előző funkcionalitását.

Az Executor felület az alap interfész. Egyetlen void execute (Futtatható parancs) metódust deklarál , amelyet egy futtatható objektum valósít meg .

Az ExecutorService felület érdekesebb. Módszerei vannak a munka befejezésének kezelésére, valamint módszerek valamilyen eredmény visszaadására. Nézzük meg közelebbről a módszereit:

Módszer Leírás
void shutdown(); A metódus meghívása leállítja az ExecutorService-t . A már feldolgozásra leadott összes feladat elvégzésre kerül, de új feladatokat nem fogadunk el.
Lista<Futtatható> shutdownNow();

A metódus meghívása leállítja az ExecutorService-t . A Thread.interrupt minden olyan feladat esetén meghívásra kerül, amelyet már feldolgozásra elküldtek. Ez a metódus a sorban álló feladatok listáját adja vissza.

A metódus nem várja meg az összes olyan feladat befejezését, amely a metódus meghívásakor "folyamatban van".

Figyelmeztetés: Ennek a metódusnak a meghívásával források szivároghatnak ki.

logikai isShutdown(); Ellenőrzi, hogy az ExecutorService le van-e állítva.
logikai isTerminated(); Igaz értéket ad vissza, ha az ExecutorService leállítása után minden feladat befejeződött . Amíg a shutdown() vagy shutdownNow() meg nem hívjuk, mindig false értéket ad vissza .
boolean awaitTermination (hosszú időtúllépés, TimeUnit egység) InterruptedExceptiont dob;

A shutdown() metódus meghívása után ez a metódus blokkolja azt a szálat, amelyen meghívásra került, amíg a következő feltételek egyike nem teljesül:

  • minden ütemezett feladat befejeződött;
  • a metódusnak átadott időtúllépés lejárt;
  • az aktuális szál megszakad.

Igaz értéket ad vissza , ha minden feladat befejeződött, és hamis értéket , ha az időtúllépés letelik a befejezés előtt.

<T> Future<T> submit(Hívható<T> feladat);

Hozzáad egy hívható feladatot az ExecutorService- hez , és visszaad egy objektumot, amely megvalósítja a Future felületet.

A <T> az átadott feladat eredményének típusa.

<T> Future<T> submit(Futtatható feladat, T eredmény);

Hozzáad egy futtatható feladatot az ExecutorService- hez , és visszaad egy objektumot, amely megvalósítja a Future felületet.

A T eredmény paraméter az, amit a get() metódus hívása visszaad az eredményülJövő tárgya.

Future<?> submit(Futtatható feladat);

Hozzáad egy futtatható feladatot az ExecutorService- hez , és visszaad egy objektumot, amely megvalósítja a Future felületet.

Ha a kapott Future objektumon meghívjuk a get() metódust , akkor nullát kapunk.

<T> List<Jövő<T>> invokeAll(Gyűjtemény<? kiterjeszti a Hívható<T>> feladatokat) InterruptedException;

A hívható feladatok listáját átadja az ExecutorService- nek . Visszaadja a Futures listát, amelyből megkaphatjuk a munka eredményét. Ez a lista akkor jelenik meg, amikor az összes beküldött feladatot befejezte.

Ha a feladatgyűjtemény módosul a metódus futása közben, akkor ennek a metódusnak az eredménye nincs meghatározva.

<T> Lista<Jövő<T>> invokeAll(Gyűjtemény<? kiterjeszti a hívható<T>> feladatokat, hosszú időtúllépés, TimeUnit egység) InterruptedException;

A hívható feladatok listáját átadja az ExecutorService- nek . Visszaadja a Futures listát, amelyből megkaphatjuk a munka eredményét. Ez a lista akkor jelenik meg, amikor az összes átadott feladat befejeződött, vagy a metódusnak átadott időtúllépés lejárta után, attól függően, hogy melyik következik be előbb.

Ha az időkorlát letelik, a befejezetlen feladatok törlődnek.

Megjegyzés: Lehetséges, hogy a törölt feladat futása nem áll le (a példában ezt a mellékhatást fogjuk látni).

Ha a feladatgyűjtemény módosul a metódus futása közben, akkor ennek a metódusnak az eredménye nincs meghatározva.

<T> T invokeAny(Collection<? expands Callable<T>> task) dob InterruptedException, ExecutionException;

A hívható feladatok listáját átadja az ExecutorService- nek . Az egyik olyan feladat eredményét adja vissza (ha van), amelyet kivétel nélkül teljesítettek (ha van ilyen).

Ha a feladatgyűjtemény módosul a metódus futása közben, akkor ennek a metódusnak az eredménye nincs meghatározva.

<T> T invokeAny(Gyűjtemény<? kiterjeszti a Hívható<T>> feladatokat, hosszú időtúllépés, TimeUnit egység) InterruptedException, ExecutionException, TimeoutException;

A hívható feladatok listáját átadja az ExecutorService- nek . Az egyik olyan feladat eredményét adja vissza (ha van), amely kivétel nélkül fejeződött be, mielőtt a metódusnak adott időtúllépés lejárt volna.

Ha a feladatgyűjtemény módosul a metódus futása közben, akkor ennek a metódusnak az eredménye nincs meghatározva.

Nézzünk egy kis példát az ExecutorService szolgáltatással való együttműködésre .


import java.util.List;
import java.util.concurrent.*;

public class ExecutorServiceTest {
   public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
//Create an ExecutorService for 2 threads
       java.util.concurrent.ExecutorService executorService = new ThreadPoolExecutor(2, 2, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10));
// Create 5 tasks
       MyRunnable task1 = new MyRunnable();
       MyRunnable task2 = new MyRunnable();
       MyRunnable task3 = new MyRunnable();
       MyRunnable task4 = new MyRunnable();
       MyRunnable task5 = new MyRunnable();

       final List<MyRunnable> tasks = List.of(task1, task2, task3, task4, task5);
// Pass a list that contains the 5 tasks we created
       final List<Future<Void>> futures = executorService.invokeAll(tasks, 6, TimeUnit.SECONDS);
       System.out.println("Futures received");

// Stop the ExecutorService
       executorService.shutdown();

       try {
           TimeUnit.SECONDS.sleep(3);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }

       System.out.println(executorService.isShutdown());
       System.out.println(executorService.isTerminated());
   }

   public static class MyRunnable implements Callable<Void> {

       @Override
       public void call() {
// Add 2 delays. When the ExecutorService is stopped, we will see which delay is in progress when the attempt is made to stop execution of the task
           try {
               TimeUnit.SECONDS.sleep(3);
           } catch (InterruptedException e) {
               System.out.println("sleep 1: " + e.getMessage());
           }
           try {
               TimeUnit.SECONDS.sleep(2);
           } catch (InterruptedException e) {
               System.out.println("sleep 2: " + e.getMessage());
           }
           System.out.println("done");
           return null;
       }
   }
}

Kimenet:

kész
kész
Határidős fogadott
alvás 1: alvás megszakítva
alvás 1: alvás megszakítva
kész
kész igaz
igaz

Minden feladat 5 másodpercig fut. Létrehoztunk egy készletet két szál számára, így a kimenet első két sora teljesen logikus.

Hat másodperccel a program indítása után az invokeAll metódus időtúllépése következik be, és az eredmény a Futures listájaként jelenik meg . Ez látható a Futures kapott kimeneti karakterláncból .

Az első két feladat elvégzése után újabb kettő kezdődik. De mivel az invokeAll metódusban beállított időkorlát letelik, ennek a két feladatnak nincs ideje befejezni. „Mégse” parancsot kapnak . Ez az oka annak, hogy a kimenetnek két sora van alvás 1-el: alvás megszakítva .

És akkor még két sort láthat a done mellett . Ez az a mellékhatás, amelyet az invokeAll metódus leírásakor említettem .

Az ötödik, egyben utolsó feladat el sem indul, így a kimenetben nem látunk róla semmit.

Az utolsó két sor az isShutdown és isTerminated metódusok meghívásának eredménye .

Az is érdekes, hogy ezt a példát hibakeresési módban futtatjuk, és megnézzük a feladat állapotát az időkorlát letelte után (állítsunk be egy töréspontot a sorban az executorService.shutdown(); ) paranccsal:

Azt látjuk, hogy két feladat normálisan befejeződött , és három feladat „törölve” lett .

ScheduledExecutorService

A végrehajtókról szóló vitánk befejezéseként vessünk egy pillantást a ScheduledExecutorService oldalra .

4 módszere van:

Módszer Leírás
public ScheduledFuture<?> ütemterv (Futtatható parancs, hosszú késleltetés, TimeUnit egység); Ütemezi az átadott futtatható feladat egyszeri futtatását az argumentumként megadott késleltetés után.
public <V> ScheduledFuture<V> menetrend(Hívható<V> hívható, hosszú késleltetés, Időegység egység); Ütemezi az átadott hívható feladat egyszeri futását az argumentumként megadott késleltetés után.
public ScheduledFuture<?> scheduleAtFixedRate(Futtatható parancs, hosszú kezdetiKésleltetés, hosszú periódus, Időegység egység); Ütemezi az átadott feladat időszakos végrehajtását, amely először a inicialDelay után kerül végrehajtásra, és minden további futtatás a period után kezdődik .
public ScheduledFuture<?> scheduleWithFixedDelay(Futtatható parancs, hosszú kezdeti késleltetés, hosszú késleltetés, időegység egység); Ütemezi az átadott feladat időszakos végrehajtását, amely először az inicialDelay után kerül végrehajtásra, és minden további futás késleltetés után kezdődik (az előző futás befejezése és az aktuális indítása közötti időszak).