Végrehajtók newCachedThreadPool () vs newFixedThreadPool ()

1. Áttekintés

Ami a szálkészlet megvalósítását illeti, a Java szabványos könyvtár rengeteg lehetőséget kínál, amelyek közül választhat. A rögzített és a gyorsítótárazott szálkészletek meglehetősen mindenütt jelen vannak a megvalósítások között.

Ebben az oktatóanyagban megnézzük, hogyan működnek a szálkészletek a motorháztető alatt, majd összehasonlítjuk ezeket a megvalósításokat és azok felhasználási eseteit.

2. Gyorsítótárazott szálkészlet

Vizsgáljuk meg, hogyan hoz létre Java a gyorsítótárazott szálkészlet, amikor hívunk Executors.newCachedThreadPool ():

public static ExecutorService newCachedThreadPool () {return new ThreadPoolExecutor (0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue ()); }

A gyorsítótárazott szálkészletek „szinkron átadást” használnak az új feladatok sorba állításához. A szinkron átadás alapgondolata egyszerű és mégis intuitív: Sorba lehet állítani egy elemet csak akkor, ha egy másik szál egyszerre veszi az elemet. Más szavakkal, a SynchronousQueue semmilyen feladatot nem tud ellátni.

Tegyük fel, hogy új feladat érkezik. Ha egy üresjárati szál vár a sorban, akkor a feladat előállítója átadja a feladatot annak a szálnak. Egyébként, mivel a sor mindig tele van, a végrehajtó új szálat hoz létre a feladat kezelésére.

A gyorsítótárazott készlet nulla szálból indul, és potenciálisan növekedhet Egész.MAX_VALUE szálak. Gyakorlatilag a gyorsítótárazott szálkészlet egyetlen korlátozása a rendelkezésre álló rendszererőforrások.

A rendszererőforrások jobb kezelése érdekében a gyorsítótárazott szálkészletek eltávolítják azokat a szálakat, amelyek egy percig tétlenek.

2.1. Használjon tokokat

A gyorsítótárazott szálkészlet konfigurációja rövid ideig gyorsítótárba helyezi a szálakat (így a nevet is), hogy újra felhasználhassa őket más feladatokra. Ennek eredményeként akkor működik a legjobban, ha ésszerű számú rövid életű feladattal foglalkozunk.

A kulcs itt „ésszerű” és „rövid életű”. Ennek a pontnak a tisztázása érdekében értékeljünk egy olyan forgatókönyvet, ahol a gyorsítótárazott készletek nem megfelelőek. Itt egymillió feladatot fogunk benyújtani, amelyek mindegyike 100 mikrosekundumot vesz igénybe a befejezéshez:

Hívható feladat = () -> {hosszú egyszázMikroszekundum = 100_000; rég elindultAt = System.nanoTime (); while (System.nanoTime () - StartAt task) .collect (toList ()); var eredmény = cachedPool.invokeAll (feladatok);

Ez sok szálat hoz létre, amelyek ésszerűtlen memóriahasználatot jelentenek, és ami még rosszabb, sok CPU-kapcsolót. Mindkét rendellenesség jelentősen rontaná az általános teljesítményt.

Ezért kerülnünk kell ezt a szálkészletet, ha a végrehajtás ideje kiszámíthatatlan, például az IO-hoz kötött feladatok.

3. Fix menetes készlet

Lássuk, hogyan működnek a rögzített menetes medencék a motorháztető alatt:

public static ExecutorService newFixedThreadPool (int nThreads) {return new ThreadPoolExecutor (nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue ()); }

A gyorsítótárazott szálkészlettel ellentétben ez egy korlátlan várólistát használ, rögzített számú, soha nem járó szálakkal. Ezért az egyre növekvő számú szál helyett a fix szálkészlet fix mennyiségű szálal próbálja végrehajtani a bejövő feladatokat. Amikor az összes szál foglalt, akkor a végrehajtó sorba állítja az új feladatokat. Így jobban ellenőrizhetjük programunk erőforrás-felhasználását.

Ennek eredményeként a rögzített szálkészletek jobban megfelelnek a kiszámíthatatlan végrehajtási idővel rendelkező feladatoknak.

4. Sajnálatos hasonlóságok

Eddig csak a gyorsítótárazott és a rögzített szálkészletek közötti különbségeket számoltuk fel.

Mindezeket a különbségeket leszámítva mindkettő használja AbortPolicy mint telítettségpolitikájuk. Ezért elvárjuk, hogy ezek a végrehajtók kivételt tegyenek, amikor nem tudnak több feladatot elfogadni, sőt sorba állítani.

Lássuk, mi történik a való világban.

A gyorsítótárazott szálkészletek extrém körülmények között továbbra is egyre több szálat hoznak létre, így gyakorlatilag soha nem érnek el telítési pontot. Hasonlóképpen, a fix szálas készletek továbbra is újabb és újabb feladatokat adnak a sorukba. Ezért a fix poolok sem érnek el soha telítési pontot.

Mivel mindkét készlet nem lesz telített, amikor a terhelés kivételesen nagy, sok memóriát fogyasztanak a szálak létrehozásához vagy a sorban állási feladatokhoz. Sérüléssel járul hozzá a sérüléshez, hogy a gyorsítótárazott szálkészletek sok processzor kontextus kapcsolót is igénybe vesznek.

Egyébként jobban ellenőrizheti az erőforrás-felhasználást, erősen ajánlott egy egyedi létrehozása ThreadPoolExecutor:

var boundedQueue = új ArrayBlockingQueue (1000); új ThreadPoolExecutor (10, 20, 60, SECONDS, boundedQueue, új AbortPolicy ()); 

Itt a szálkészletünk legfeljebb 20 szálat tartalmazhat, és legfeljebb 1000 feladatot tud sorba állítani. Ezenkívül, ha nem képes tovább elfogadni a terhelést, egyszerűen kivételt vet.

5. Következtetés

Ebben az oktatóanyagban bepillantást nyerhettünk a JDK forráskódjába, hogy lássuk, mennyire más Végrehajtó s munka a motorháztető alatt. Ezután összehasonlítottuk a rögzített és a gyorsítótárazott szálkészleteket és azok felhasználási eseteit.

Végül megpróbáltuk megoldani az egyedi szálkészletekkel rendelkező készletek ellenőrzésen kívüli erőforrás-felhasználását.


$config[zx-auto] not found$config[zx-overlay] not found