A java.util.concurrent áttekintése

1. Áttekintés

A java.util.egyidejű csomag egyidejű alkalmazások létrehozásának eszközeit kínálja.

Ebben a cikkben áttekintjük a teljes csomagot.

2. Fő alkotóelemek

A java.util.egyidejű túl sok funkciót tartalmaz ahhoz, hogy egyetlen írásban megvitassák. Ebben a cikkben elsősorban a csomag leghasznosabb segédprogramjaira fogunk összpontosítani, például:

  • Végrehajtó
  • ExecutorService
  • ScheduledExecutorService
  • Jövő
  • CountDownLatch
  • Ciklikus akadály
  • Szemafor
  • ThreadFactory
  • BlockingQueue
  • DelayQueue
  • Zárak
  • Phaser

Itt számos dedikált cikket találhat az egyes osztályok számára.

2.1. Végrehajtó

Végrehajtó olyan felület, amely egy objektumot képvisel, amely végrehajtja a megadott feladatokat.

Az adott megvalósítástól (honnan indul az invokáció) függ, hogy a feladatot új vagy aktuális szálon kell-e futtatni. Ezért ennek az interfésznek a segítségével leválaszthatjuk a feladat végrehajtási folyamatát a tényleges feladat végrehajtási mechanizmusáról.

Itt érdemes megjegyezni, hogy Végrehajtó nem követeli szigorúan, hogy a feladat végrehajtása aszinkron legyen. A legegyszerűbb esetben a végrehajtó azonnal meghívhatja a beküldött feladatot a meghívó szálban.

Létre kell hoznunk egy meghívót a végrehajtó példány létrehozásához:

public class Invoker végrehajtja az Executort {@Override public void execute (Runnable r) {r.run (); }}

Most ezt a meghívót használhatjuk a feladat végrehajtására.

public void execute () {Végrehajtó végrehajtó = új Invoker (); végrehajtó.execute (() -> {// végrehajtandó feladat}); }

Megjegyzendő, hogy ha a végrehajtó nem tudja elfogadni a feladatot végrehajtásra, akkor dobni fog RejectedExecutionException.

2.2. ExecutorService

ExecutorService egy komplett megoldás az aszinkron feldolgozáshoz. Kezeli a memóriában lévő várólistát és ütemezi a beküldött feladatokat a szál elérhetősége alapján.

Használni ExecutorService, létre kell hoznunk egyet Futható osztály.

public class Task megvalósítja a Runnable {@Override public void run () {// feladat részletei}}

Most létrehozhatjuk a ExecutorService példányt, és rendelje hozzá ezt a feladatot. A létrehozáskor meg kell adnunk a szálkészlet méretét.

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

Ha egyszálúat akarunk létrehozni ExecutorService például használhatjuk newSingleThreadExecutor (ThreadFactory threadFactory) hogy létrehozza a példányt.

A végrehajtó létrehozása után felhasználhatjuk a feladat elküldésére.

public void execute () {végrehajtó.submit (új Feladat ()); }

Hozhatunk létre a Futható például a feladat elküldése közben.

végrehajtó.beküldés (() -> {új Feladat ();});

Két dobozon kívüli végrehajtás-befejezési módszerrel is jár. Az első az Leállitás(); megvárja, amíg az összes beküldött feladat végrehajtása befejeződik. A másik módszer az shutdownNow () amih azonnal befejezi az összes függőben lévő / végrehajtott feladatot.

Van egy másik módszer is awaitTermination (hosszú időtúllépés, TimeUnit egység) amely erőteljesen blokkol, amíg az összes feladat végrehajtása befejeződik egy leállási esemény kiváltása vagy a végrehajtás-időtúllépés után, vagy maga a végrehajtási szál megszakad,

próbáld ki a {végrehajtó.awaitTermination (20l, TimeUnit.NANOSECONDS); } catch (InterruptedException e) {e.printStackTrace (); }

2.3. ScheduledExecutorService

ScheduledExecutorService hasonló felület a ExecutorService, de időszakosan képes elvégezni a feladatokat.

Executor és ExecutorServiceMódszereit a helyszínen ütemezik, mesterséges késedelem nélkül. A nulla vagy bármely negatív érték azt jelzi, hogy a kérést azonnal végre kell hajtani.

Használhatjuk mindkettőt Futható és Hívható interfész a feladat meghatározásához.

public void execute () {ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor (); Jövő jövő = végrehajtóSzolgáltatás.schedule (() -> {// ... return "Hello world";}, 1, TimeUnit.SECONDS); ScheduledFuture tervezettFuture = végrehajtóSzolgáltatás.schedule (() -> {// ...}, 1, TimeUnit.SECONDS); executorService.shutdown (); }

ScheduledExecutorService ütemezheti a feladatot is bizonyos adott késés után:

végrehajtóService.scheduleAtFixedRate (() -> {// ...}, 1, 10, TimeUnit.SECONDS); végrehajtóService.scheduleWithFixedDelay (() -> {// ...}, 1, 10, TimeUnit.SECONDS);

Itt a scheduleAtFixedRate (Futható parancs, hosszú kezdeti késedelem, hosszú időszak, TimeUnit egység) A módszer létrehoz és végrehajt egy időszakos műveletet, amelyet először a megadott kezdeti késleltetés után, majd a megadott periódussal hívnak meg a szolgáltatási példány leállításáig.

A scheduleWithFixedDelay (Futható parancs, hosszú kezdeti késleltetés, hosszú késleltetés, TimeUnit egység) A módszer létrehoz és végrehajt egy periodikus műveletet, amelyet először a megadott kezdeti késleltetés után hívnak meg, és ismételten az adott késéssel a végrehajtó befejezése és a következő meghívása között.

2.4. Jövő

Jövő az aszinkron művelet eredményének ábrázolására szolgál. Módszerekkel jár annak ellenőrzésére, hogy az aszinkron művelet befejeződött-e vagy sem, a kiszámított eredmény megszerzéséhez stb.

Sőt, a törlés (logikai érték mayInterruptIfRunning) Az API törli a műveletet és felszabadítja a végrehajtó szálat. Ha az értéke mayInterruptIfRunning igaz, a feladatot végrehajtó szál azonnal leáll.

Ellenkező esetben a folyamatban lévő feladatok végrehajtása engedélyezett.

Az alábbi kódrészletet használhatjuk egy jövőbeli példány létrehozására:

public void invoke () {ExecutorService végrehajtóService = Executors.newFixedThreadPool (10); Jövőbeli jövő = végrehajtóSzolgáltatás.submit (() -> {// ... Szál.alszik (10000l); return "Hello világ";}); }

A következő kódrészlettel ellenőrizhetjük, hogy a jövőbeli eredmény készen áll-e, és ha a számítás megtörtént, lekérjük az adatokat:

if (future.isDone () &&! future.isCancelled ()) {try {str = jövő.get (); } catch (InterruptedException | ExecutionException e) {e.printStackTrace (); }}

Megadhatunk egy időzítést is egy adott művelethez. Ha a feladat ennél több időt vesz igénybe, a TimeoutException dobják:

próbáld ki a {future.get (10, TimeUnit.SECONDS); } catch (InterruptedException | ExecutionException | TimeoutException e) {e.printStackTrace (); }

2.5. CountDownLatch

CountDownLatch (bevezetett JDK 5) egy segédprogram, amely blokkolja a szálak készletét, amíg valamilyen művelet befejeződik.

A CountDownLatch inicializálva van a számláló (egész szám típus); ez a számláló csökken, mivel a függő szálak befejezik a végrehajtást. De miután a számláló eléri a nullát, más szálak szabadulnak fel.

Tudjon meg többet arról CountDownLatch itt.

2.6. Ciklikus akadály

Ciklikus akadály majdnem ugyanúgy működik, mint CountDownLatch kivéve, hogy újra felhasználhatjuk. nem úgy mint CountDownLatch, lehetővé teszi, hogy több szál megvárja egymást a várják() módszer (barrier feltétel néven ismert), mielőtt a végső feladatra hivatkoznánk.

Létre kell hoznunk a Futható feladatpéldány a korlátfeltétel elindításához:

nyilvános osztály Feladat megvalósítja Futható {magán CyclicBarrier akadály; nyilvános feladat (CyclicBarrier barrier) {this.barrier = akadály; } @Orride public void run () {try {LOG.info (Thread.currentThread (). GetName () + "vár"); barrier.await (); LOG.info (Thread.currentThread (). GetName () + "el van engedve"); } catch (InterruptedException | BrokenBarrierException e) {e.printStackTrace (); }}}

Most néhány szálat meghívhatunk, hogy versenyezzünk az akadályfeltételért:

public void start () {CyclicBarrier cyclicBarrier = new CyclicBarrier (3, () -> {// ... LOG.info ("Minden előző feladat befejeződött");}); Szál t1 = új szál (új feladat (ciklusos akadály), "T1"); Szál t2 = új szál (új feladat (ciklusos akadály), "T2"); Szál t3 = új szál (új feladat (ciklusos akadály), "T3"); if (! cyclicBarrier.isBroken ()) {t1.start (); t2.start (); t3.start (); }}

Itt a törött() A metódus ellenőrzi, hogy valamelyik szál megszakadt-e a végrehajtási idő alatt. Ezt az ellenőrzést mindig a tényleges folyamat elvégzése előtt kell elvégeznünk.

2.7. Szemafor

A Szemafor a szálszintű hozzáférés blokkolására szolgál a fizikai vagy logikai erőforrás bizonyos részeihez. A szemafor számos engedélyt tartalmaz; valahányszor egy szál megpróbál belépni a kritikus szakaszba, ellenőriznie kell a szemaforot, van-e engedély vagy sem.

Ha engedély nem áll rendelkezésre (via tryAcquire ()), a szál nem engedhető átugrani a kritikus szakaszba; ha azonban az engedély rendelkezésre áll, akkor a hozzáférést megadják, és az engedély számlálója csökken.

Amint a végrehajtó szál elengedi a kritikus részt, az engedélyszámláló ismét növekszik ( kiadás() módszer).

Megadhatjuk a hozzáférés megszerzésének időkorlátját a tryAcquire (hosszú időtúllépés, TimeUnit egység) módszer.

Ellenőrizhetjük a rendelkezésre álló engedélyek számát vagy a szemafor megszerzésére váró szálak számát is.

A következő kódrészlet használható szemafor megvalósítására:

statikus szemafor szemafor = új szemafor (10); public void execute () dobja az InterruptedException {LOG.info ("Rendelkezésre álló engedély:" + semaphore.availablePermits ()); LOG.info ("A megszerzésre váró szálak száma:" + szemafor.getQueueLength ()); if (szemafor.tryAcquire ()) {próbáld meg {// ...} végül {szemafor.release (); }}}

Meg tudjuk valósítani a Mutex mint az adatstruktúra használata Szemafor. További részletek itt találhatók.

2.8. ThreadFactory

Ahogy a neve is sugallja, ThreadFactory szál (nem létező) készletként működik, amely igény szerint új szálat hoz létre. Ez kiküszöböli a sok kazánlemez-kódolás szükségességét a hatékony szálalkotó mechanizmusok megvalósításához.

Meghatározhatjuk a ThreadFactory:

public class BaeldungThreadFactory implementálja a ThreadFactory {private int threadId; privát karakterlánc neve; public BaeldungThreadFactory (karakterlánc neve) {threadId = 1; ez.név = név; } @Orride public Thread newThread (Futható r) {Thread t = new Thread (r, név + "-Thread_" + threadId); LOG.info ("létrehozott új szál azonosítóval:" + threadId + "és név:" + t.getName ()); threadId ++; visszatér t; }}

Ezt használhatjuk newThread (Futható r) módszer új szál létrehozásához futás közben:

BaeldungThreadFactory gyár = új BaeldungThreadFactory ("BaeldungThreadFactory"); for (int i = 0; i <10; i ++) {szál t = gyár.newThread (új feladat ()); t.start (); }

2.9. BlockingQueue

Az aszinkron programozásban az egyik leggyakoribb integrációs minta a gyártó-fogyasztó mintázat. A java.util.egyidejű A csomag egy ismert adatstruktúrával érkezik BlockingQueue - ami nagyon hasznos lehet ezekben az aszinkron forgatókönyvekben.

További információ és működő példa itt érhető el.

2.10. DelayQueue

DelayQueue egy végtelen méretű blokkoló sor az elemekről, ahol egy elem csak akkor húzható meg, ha lejárati ideje (a felhasználó által definiált késleltetés) eltelik. Ezért a legfelső elem (fej) lesz a legtöbb késedelem, és utoljára fogják lekérdezni.

További információ és működő példa itt érhető el.

2.11. Zárak

Nem meglepő módon, Zár egy segédprogram, amely megakadályozza, hogy más szálak hozzáférjenek a kód bizonyos szegmenséhez, kivéve azokat a szálakat, amelyek jelenleg végrehajtják.

A zár és a szinkronizált blokk közötti fő különbség az, hogy a szinkronizált blokk teljes egészében benne van a módszerben; azonban a Lock API lock () és unlock () működését külön módszerekkel végezhetjük.

További információ és működő példa itt érhető el.

2.12. Phaser

Phaser rugalmasabb megoldás, mint Ciklikus akadály és CountDownLatch - újrafelhasználható korlátként szolgál, amelyre a szálak dinamikus számának meg kell várnia a végrehajtás folytatása előtt. A végrehajtás több szakaszát koordinálhatjuk, a Phaser az egyes programfázisokhoz.

További információ és működő példa itt érhető el.

3. Következtetés

Ebben a magas szintű, áttekintő cikkben a rendelkezésre álló különféle segédprogramokra összpontosítottunk java.util.egyidejű csomag.

Mint mindig, a teljes forráskód elérhető a GitHubon.