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.