Hogyan indítsunk el egy szálat a Java-ban
1. Bemutatkozás
Ebben az oktatóanyagban különböző módokat fogunk felfedezni egy szál elindításához és párhuzamos feladatok végrehajtásához.
Ez nagyon hasznos, különösen olyan hosszú vagy ismétlődő műveletek esetén, amelyek nem futtathatók a fő szálon, vagy ahol a felhasználói felület interakcióját nem lehet várakozni a művelet eredményeire várva.
Ha többet szeretne megtudni a szálak részleteiről, feltétlenül olvassa el a Java szálak életciklusáról szóló oktatóanyagunkat.
2. A szál futtatásának alapjai
Könnyedén megírhatunk néhány logikát, amely párhuzamos szálban fut, a cérna keretrendszer.
Próbálkozzunk egy alapvető példával a cérna osztály:
public class A NewThread kiterjeszti a Threadot {public void run () {long startTime = System.currentTimeMillis (); int i = 0; while (true) {System.out.println (this.getName () + ": Új szál fut ..." + i ++); próbáld meg {// Várjon egy másodpercet, hogy ne nyomtasson túl gyorsan Thread.sleep (1000); } catch (InterruptedException e) {e.printStackTrace (); } ...}}}
És most írunk egy második osztályt a szál inicializálásához és elindításához:
public class SingleThreadExample {public static void main (String [] args) {NewThread t = new NewThread (); t.start (); }}
Fel kellene hívnunk a Rajt() módszer a ÚJ állapot (a meg nem indult egyenértékű). Ellenkező esetben a Java dob egy példányt IllegalThreadStateException kivétel.
Tegyük fel, hogy több szálat kell elindítanunk:
public class MultipleThreadsExample {public static void main (String [] args) {NewThread t1 = new NewThread (); t1.setName ("MyThread-1"); NewThread t2 = new NewThread (); t2.setName ("MyThread-2"); t1.start (); t2.start (); }}
Kódunk még mindig nagyon egyszerűnek tűnik, és nagyon hasonlít az online példákhoz.
Természetesen, ez messze nem a gyártásra kész kód, ahol kritikus fontosságú az erőforrások helyes kezelése, a túl sok kontextusváltás vagy a túl sok memóriahasználat elkerülése érdekében.
Tehát, hogy készen álljunk a gyártásra, most meg kell írnunk egy kiegészítő kazántáblát foglalkozni vele:
- új szálak következetes létrehozása
- az egyidejűleg élő szálak száma
- a szálak elosztása: nagyon fontos a démonláncok számára a szivárgások elkerülése érdekében
Ha akarjuk, megírhatjuk a saját kódunkat ezekhez az esetekhez és még néhányhoz, de miért kellene újra feltalálni a kereket?
3. Az ExecutorService Keretrendszer
A ExecutorService megvalósítja a szálkészlet tervezési mintáját (más néven replikált munkásnak vagy munkás-legénység modellnek), és gondoskodik a fent említett szálkezelésről, ráadásul néhány nagyon hasznos funkcióval egészíti ki, mint például a szál újrafelhasználhatósága és a feladatsorok.
Különösen a szál újrafelhasználhatósága nagyon fontos: egy nagyszabású alkalmazásban sok szál objektum allokálása és elosztása jelentős memóriakezelési költségeket eredményez.
Munkásszálakkal minimalizáljuk a szál létrehozása által okozott általános költségeket.
A medence konfigurációjának megkönnyítése érdekében ExecutorService könnyű konstruktorral és néhány testreszabási lehetőséggel érkezik, mint például a sor típusa, a szálak minimális és maximális száma, valamint elnevezési szokásuk.
További részletek a ExecutorService, kérjük, olvassa el a Java ExecutorService útmutatót.
4. Feladat indítása végrehajtókkal
Ennek az erőteljes keretrendszernek köszönhetően átállhatjuk gondolkodásmódunkat a kezdő szálakról a feladatok benyújtására.
Nézzük meg, hogyan nyújthatunk be aszinkron feladatot a végrehajtónknak:
ExecutorService végrehajtó = Executors.newFixedThreadPool (10); ... végrehajtó.submit (() -> {új Feladat ();});
Kétféle módszert használhatunk: végrehajtani, amely semmit sem ad vissza, és Beküldés, amely a Jövő összefoglalja a számítás eredményét.
További információ a Határidős, kérjük, olvassa el a java.util.concurrent.Future útmutatónkat.
5. Feladat indítása CompletableFutures
A végeredmény lekérése a Jövő objektumot használhatjuk kap Az objektumban elérhető módszer, de ez blokkolja a szülőszálat a számítás végéig.
Alternatív megoldásként elkerülhetjük a blokkot azzal, hogy több logikát adunk a feladatunkhoz, de növelnünk kell a kódunk összetettségét.
A Java 1.8 új keretrendszert vezetett be a Jövő a számítás eredményének jobb működése érdekében: a CompletableFuture.
CompletableFuture megvalósítja CompletableStage, amely hatalmas választékot kínál a visszahívások csatolásához, és elkerüli az összes olyan vízvezeték-szerelést, amely szükséges a műveletek futtatásához az eredményen, miután elkészült.
A feladat elküldéséhez sokkal egyszerűbb a megvalósítás:
CompletableFuture.supplyAsync (() -> "Hello");
supplyAsync vesz egy Támogató a kódot tartalmazza, amelyet aszinkron módon akarunk végrehajtani - esetünkben a lambda paramétert.
A feladatot most implicit módon elküldtük a ForkJoinPool.commonPool (), vagy megadhatjuk a Végrehajtó második paraméterként előnyben részesítjük.
Hogy többet tudjon meg CompletableFuture, kérjük, olvassa el a CompletableFuture útmutatónkat.
6. Késleltetett vagy időszakos feladatok futtatása
Ha összetett webalkalmazásokkal dolgozunk, akkor előfordulhat, hogy feladatokat kell futtatnunk meghatározott időpontokban, esetleg rendszeresen.
A Java-nak kevés olyan eszköze van, amely segít késleltetett vagy ismétlődő műveletek futtatásában:
- java.util.Timer
- java.util.concurrent.ScheduledThreadPoolExecutor
6.1. Időzítő
Időzítő egy olyan lehetőség, amellyel a feladatokat a jövőbeli végrehajtáshoz háttérszálon lehet ütemezni.
A feladatok ütemezhetők egyszeri végrehajtásra, vagy rendszeres időközönként ismételt végrehajtásra.
Lássuk, hogy néz ki a kód, ha egy másodperc késés után akarunk futtatni egy feladatot:
TimerTask task = new TimerTask () {public void run () {System.out.println ("A feladat végrehajtása:" + új dátum () + "n" + "A szál neve:" + Thread.currentThread (). GetName ( )); }}; Időzítő időzítő = új időzítő ("Időzítő"); hosszú késés = 1000L; timer.schedule (feladat, késés);
Most adjunk hozzá egy ismétlődő ütemtervet:
timer.scheduleAtFixedRate (repeatTask, delay, period);
Ezúttal a feladat a megadott késés után fog futni, és az eltelt idő után ismétlődik.
További információkért olvassa el a Java Timer útmutatót.
6.2. ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor hasonló módszerekkel rendelkezik Időzítő osztály:
ScheduledExecutorService executorService = Executors.newScheduledThreadPool (2); ScheduledFuture resultFuture = végrehajtóSzolgáltatás.schedule (callableTask, 1, TimeUnit.SECONDS);
Példánk végére használjuk scheduleAtFixedRate () ismétlődő feladatokhoz:
ScheduledFuture resultFuture = végrehajtóService.scheduleAtFixedRate (runnableTask, 100, 450, TimeUnit.MILLISECONDS);
A fenti kód egy 100 milliszekundumos kezdeti késés után hajt végre egy feladatot, és ezt követően ugyanazt a feladatot hajtja végre minden 450 milliszekundumban.
Ha a processzor nem tudja befejezni a feladat feldolgozását időben a következő előfordulás előtt, akkor a ScheduledExecutorService a következő feladat megkezdése előtt megvárja az aktuális feladat befejezését.
Ennek a várakozási időnek a elkerülése érdekében használhatjuk scheduleWithFixedDelay (), amely a nevével leírva rögzített hosszúságú késést garantál a feladat ismétlései között.
További részletek a ScheduledExecutorService, kérjük, olvassa el a Java ExecutorService útmutatót.6.3. Melyik eszköz jobb?
Ha a fenti példákat futtatjuk, a számítás eredménye ugyanaz lesz.
Így, hogyan válasszuk ki a megfelelő eszközt?
Ha egy keretrendszer több választási lehetőséget kínál, fontos megérteni a mögöttes technológiát, hogy megalapozott döntést hozzon.
Próbáljunk meg merülni egy kicsit mélyebben a motorháztető alatt.
Időzítő:
- nem kínál valós idejű garanciákat: a feladatokat ütemezi a Object.wait (hosszú) módszer
- egyetlen háttérszál van, így a feladatok egymás után futnak, és egy hosszú ideje futó feladat késleltetheti másokat
- futásidejű kivételeket dobtak be a TimerTask megöli az egyetlen elérhető szálat, így megöli Időzítő
ScheduledThreadPoolExecutor:
- tetszőleges számú szálral konfigurálható
- kihasználhatja az összes rendelkezésre álló CPU magot
- futásidejű kivételeket fog meg, és lehetővé teszi, hogy kezeljük őket, ha akarjuk (felülírva afterExecute módszer től ThreadPoolExecutor)
- törli a kivételt eldöntő feladatot, miközben hagyja, hogy mások tovább futjanak
- az operációs rendszer ütemezési rendszerére támaszkodik az időzónák, a késések, a szoláris idő stb.
- együttműködési API-t biztosít, ha több feladat közötti koordinációra van szükségünk, például várni kell az összes beküldött feladat befejezésére
- jobb API-t biztosít a szál életciklusának kezeléséhez
A választás most kézenfekvő, igaz?
7. Különbség Jövő és ScheduledFuture
Kódpéldáinkban ezt megfigyelhetjük ScheduledThreadPoolExecutor egy adott típusú Jövő: ScheduledFuture.
ScheduledFuture kiterjeszti mindkettőt Jövő és Késleltetett interfészek, így örökölve a további módszert getDelay amely visszaadja az aktuális feladathoz társított fennmaradó késleltetést. Meghosszabbította RunnableScheduledFuture amely hozzáad egy módszert annak ellenőrzésére, hogy a feladat időszakos-e.
ScheduledThreadPoolExecutor ezeket a konstrukciókat a belső osztályon keresztül valósítja meg ScheduledFutureTask és felhasználja őket a feladat életciklusának irányítására.
8. Következtetések
Ebben az oktatóanyagban kísérleteztünk a rendelkezésre álló különböző keretekkel a szálak indításához és a feladatok párhuzamos futtatásához.
Aztán elmélyültünk a különbségek között Időzítő és ScheduledThreadPoolExecutor.
A cikk forráskódja elérhető a GitHubon.