Útmutató a Java ExecutorService szolgáltatáshoz

1. Áttekintés

ExecutorService a JDK által biztosított keretrendszer, amely egyszerűsíti a feladatok végrehajtását aszinkron módban. Általában véve, ExecutorService automatikusan szálkészletet és API-t biztosít a feladatok hozzárendeléséhez.

2. Instantálás ExecutorService

2.1. A gyár módszerei Végrehajtók Osztály

A létrehozás legegyszerűbb módja ExecutorService az egyik gyári módszer használata Végrehajtók osztály.

Például a következő kódsor létrehoz egy szálkészletet 10 szálból:

ExecutorService végrehajtó = Executors.newFixedThreadPool (10);

Ez számos más gyári módszer az előre definiált létrehozáshoz ExecutorService amelyek megfelelnek a konkrét felhasználási eseteknek. Az Ön igényeinek leginkább megfelelő módszer megtalálásához olvassa el az Oracle hivatalos dokumentációját.

2.2. Közvetlenül hozzon létre egy ExecutorService

Mivel ExecutorService egy felület, bármely megvalósításának példánya használható. Számos megvalósítás közül lehet választani a java.util.egyidejű csomagot, vagy létrehozhat saját.

Például a ThreadPoolExecutor osztály rendelkezik néhány konstruktorral, amelyek segítségével konfigurálható egy végrehajtó szolgáltatás és annak belső készlet.

ExecutorService végrehajtóService = új ThreadPoolExecutor (1, 1, 0L, TimeUnit.MILLISECONDS, új LinkedBlockingQueue ());

Észreveheti, hogy a fenti kód nagyon hasonlít a gyári módszer forráskódjához newSingleThreadExecutor (). A legtöbb esetben nincs szükség részletes manuális konfigurálásra.

3. Feladatok hozzárendelése a ExecutorService

ExecutorService végre tudja hajtani Futható és Hívható feladatok. A cikk egyszerűsítése érdekében két primitív feladatot fogunk használni. Vegye figyelembe, hogy itt lambda kifejezéseket használnak anonim belső osztályok helyett:

Futható runnableTask = () -> {próbáld meg {TimeUnit.MILLISECONDS.sleep (300); } catch (InterruptedException e) {e.printStackTrace (); }}; Hívható callableTask = () -> {TimeUnit.MILLISECONDS.sleep (300); return "Feladat végrehajtása"; }; Lista callableTasks = new ArrayList (); callableTasks.add (callableTask); callableTasks.add (callableTask); callableTasks.add (callableTask);

Feladatok rendelhetők a ExecutorService több módszer alkalmazásával, többek között végrehajtani (), amelyet a Végrehajtó interfész, valamint Beküldés(), invokeAny (), invokeAll ().

A végrehajtani () módszer az üres, és nem ad lehetőséget a feladat végrehajtásának eredményének megszerzésére, vagy a feladat állapotának ellenőrzésére (fut vagy végrehajtódik-e).

executorService.execute (runnableTask);

Beküldés() benyújtja a Hívható vagy a Futható feladat egy ExecutorService és egy type eredményt ad vissza Jövő.

Jövő jövő = végrehajtóSzolgáltatás.submit (callableTask);

invokeAny () feladatgyűjteményt rendel hozzá egy ExecutorService, mindegyik végrehajtását okozza, és egy feladat sikeres végrehajtásának eredményét adja vissza (ha sikeres végrehajtás történt).

Karakterlánc eredménye = executorService.invokeAny (callableTasks);

invokeAll () feladatgyűjteményt rendel hozzá egy ExecutorService, ami mindegyik végrehajtását előidézi, és az összes feladat végrehajtásának eredményét típusú objektumok listája formájában adja vissza Jövő.

Lista futures = végrehajtóService.invokeAll (callableTasks);

Mielőtt továbblépnénk, még két dolgot meg kell vitatni: az an leállítását ExecutorService és foglalkozik vele Jövő visszatérési típusok.

4. An leállítása ExecutorService

Általában a ExecutorService nem kerül automatikusan megsemmisítésre, ha nincs feldolgozandó feladat. Életben marad, és várja az új munkát.

Bizonyos esetekben ez nagyon hasznos; például ha egy alkalmazásnak szabálytalanul megjelenő feladatokat kell feldolgoznia, vagy a fordítás időpontjában nem ismert ezeknek a feladatoknak a mennyisége.

Másrészt egy alkalmazás elérheti a végét, de nem áll le, mert egy várakozás ExecutorService a JVM tovább fog futni.

Annak megfelelő leállításához ExecutorService, megvan a Leállitás() és shutdownNow () API-k.

A Leállitás()módszer nem okozza a ExecutorService. Ez teszi a ExecutorService hagyja abba az új feladatok elfogadását, és állítsa le, miután az összes futó szál befejezte jelenlegi munkáját.

executorService.shutdown ();

A shutdownNow () módszer megpróbálja elpusztítani a ExecutorService azonnal, de ez nem garantálja, hogy az összes futó szál egyszerre leáll. Ez a módszer a feldolgozásra váró feladatok listáját adja vissza. A fejlesztőnek kell eldöntenie, hogy mit kezdjen ezekkel a feladatokkal.

List notExecutedTasks = végrehajtóService.shutDownNow ();

Az egyik jó módszer a ExecutorService (amelyet az Oracle is javasol) mindkét módszer és az awaitTermination () módszer. Ezzel a megközelítéssel a ExecutorService először abbahagyja az új feladatok elvégzését, majd egy meghatározott időtartamra vár minden feladat elvégzésére. Ha ez az idő lejár, a végrehajtást azonnal leállítják:

executorService.shutdown (); próbáld meg {if (! végrehajtóService.awaitTermination (800, TimeUnit.MILLISECONDS)) {végrehajtóService.shutdownNow (); }} catch (InterruptedException e) {végrehajtóSzolgáltatás.shutdownNow (); }

5. A Jövő Felület

A Beküldés() és invokeAll () A metódusok egy objektumot vagy egy típusú objektumok gyűjteményét adják vissza Jövő, amely lehetővé teszi számunkra, hogy megkapjuk a feladat végrehajtásának eredményét, vagy ellenőrizzük a feladat állapotát (fut vagy futtatva).

A Jövő interfész egy speciális blokkolási módszert biztosít kap() amely a. tényleges eredményét adja vissza Hívható feladat végrehajtása vagy nulla abban az esetben Futható feladat. Felhívás a kap() metódus, amíg a feladat még fut, a végrehajtást blokkolni fogja, amíg a feladat megfelelően végrehajtásra nem kerül, és az eredmény nem lesz elérhető.

Jövő jövő = végrehajtóSzolgáltatás.submit (callableTask); Karakterlánc eredménye = null; próbáld ki az {eredmény = jövő.get (); } catch (InterruptedException | ExecutionException e) {e.printStackTrace (); }

A. Által okozott nagyon hosszú blokkolással kap() módszerrel az alkalmazás teljesítménye romolhat. Ha a kapott adatok nem döntő fontosságúak, időtúllépésekkel elkerülhető az ilyen probléma:

Karakterlánc eredménye = future.get (200, TimeUnit.MILLISECONDS);

Ha a végrehajtási idő hosszabb, mint a megadott (ebben az esetben 200 ezredmásodperc), a TimeoutException dobni fogják.

A kész() metódus segítségével ellenőrizhető, hogy a hozzárendelt feladat már feldolgozásra került-e vagy sem.

A Jövő interfész rendelkezik a feladat végrehajtásának törléséről is a megszünteti() módszerrel, és a lemondást a isCancelled () módszer:

logikai törölt = jövő.törlés (igaz); boolean isCancelled = jövő.isCancelled ();

6. A ScheduledExecutorService Felület

A ScheduledExecutorService előre meghatározott késedelem után és / vagy időszakosan futtatja a feladatokat. Ismét a legjobb módja annak, hogy a ScheduledExecutorService az a gyári módszerek használata Végrehajtók osztály.

Ehhez a szakaszhoz a ScheduledExecutorService egy szálat használunk:

ScheduledExecutorService executorService = Végrehajtók .newSingleThreadScheduledExecutor ();

Egyetlen feladat végrehajtásának ütemezése fix késleltetés után nekünk a Ütemezett() módszere ScheduledExecutorService. Van két Ütemezett() módszerek, amelyek lehetővé teszik a végrehajtást Futható vagy Hívható feladatok:

Future resultFuture = végrehajtóService.schedule (callableTask, 1, TimeUnit.SECONDS);

A scheduleAtFixedRate () A módszer lehetővé teszi egy feladat időszakos végrehajtását fix késleltetés után. A fenti kód egy másodpercet késik a végrehajtás előtt callableTask.

A következő kódblokk 100 milliszekundumos kezdeti késés után hajt végre egy feladatot, és ezt követően ugyanazt a feladatot hajtja végre 450 milliszekundumonként. Ha a processzornak több időre van szüksége egy hozzárendelt feladat végrehajtásához, mint a időszak paramétere scheduleAtFixedRate () módszer, az ScheduledExecutorService várni fog az aktuális feladat befejezéséig, mielőtt elindítaná a következőt:

Future resultFuture = service .scheduleAtFixedRate (runnableTask, 100, 450, TimeUnit.MILLISECONDS);

Ha a feladat ismétlése között rögzített hosszúságú késésre van szükség, scheduleWithFixedDelay () kell használni. Például a következő kód 150 milliszekundumos szünetet garantál az aktuális végrehajtás vége és egy másik kezdete között.

service.scheduleWithFixedDelay (feladat, 100, 150, TimeUnit.MILLISECONDS);

Szerint a scheduleAtFixedRate () és scheduleWithFixedDelay () módszeres szerződések esetén a feladat időbeli végrehajtása a ExecutorService vagy ha a feladat végrehajtása során kivételt hoznak.

7. ExecutorService vs. Villa / Csatlakozás

A Java 7 megjelenése után sok fejlesztő úgy döntött, hogy a ExecutorService keretet fel kell cserélni a fork / join kerettel. Ez azonban nem mindig helyes döntés. Az egyszerű használat és a villával / csatlakozással járó gyakori teljesítménynövekedés ellenére csökken a fejlesztői vezérlés mennyisége az egyidejű végrehajtás felett is.

ExecutorService lehetővé teszi a fejlesztő számára, hogy ellenőrizze a létrehozott szálak számát és a feladatok részletességét, amelyeket külön szálakkal kell végrehajtani. A legjobb felhasználási eset ExecutorService független feladatok, például tranzakciók vagy kérések feldolgozása az „egy szál egy feladathoz” séma szerint.

Ezzel szemben az Oracle dokumentációja szerint a villát / összekapcsolást a munka felgyorsítására tervezték, amely rekurzívan kisebb darabokra bontható.

8. Következtetés

Még a viszonylagos egyszerűség ellenére is ExecutorService, van néhány gyakori buktató. Összefoglaljuk őket:

Használatlanul tartani ExecutorService élő: Részletes magyarázat található a cikk 4. szakaszában arról, hogyan kell leállítani az ExecutorService;

Helytelen menet-medence kapacitás rögzített hosszúságú menet-medence használata közben: Nagyon fontos meghatározni, hogy az alkalmazásnak hány szálra lesz szüksége a feladatok hatékony végrehajtásához. A túl nagy szálkészlet felesleges többletköltségeket okoz csak a szálak létrehozásához, amelyek többnyire várakozási módban lesznek. Túl kevesen tehetik úgy, hogy egy alkalmazás nem reagál a sorban lévő feladatok hosszú várakozási ideje miatt;

Hívás a Jövő’S kap() módszer a feladat törlése után: A már törölt feladat eredményének megszerzésére tett kísérlet elindítja a CancellationException.

Váratlanul hosszú blokkolás Jövő’S kap() módszer: A váratlan várakozások elkerülése érdekében időtúllépéseket kell használni.

A cikk kódja elérhető a GitHub-tárházban.