A szálkészlet másik típusa a "gyorsítótárazott". Az ilyen szálkészleteket ugyanolyan gyakran használják, mint a rögzítetteket.

Ahogy a név is jelzi, ez a fajta szálkészlet gyorsítótárazza a szálakat. Korlátozott ideig életben tartja a fel nem használt szálakat annak érdekében, hogy ezeket a szálakat újra felhasználhassa új feladatok végrehajtására. Egy ilyen szálkészlet akkor a legjobb, ha ésszerű mennyiségű könnyű munkánk van.

A "némi ésszerű összeg" jelentése meglehetősen tág, de tudnia kell, hogy egy ilyen készlet nem alkalmas minden feladatra. Tegyük fel például, hogy millió feladatot szeretnénk létrehozni. Még ha mindegyik nagyon kevés időt vesz is igénybe, akkor is ésszerűtlenül sok erőforrást használunk fel, és rontjuk a teljesítményt. El kell kerülnünk az ilyen készleteket is, ha a végrehajtási idő kiszámíthatatlan, például I/O feladatoknál.

A burkolat alatt a ThreadPoolExecutor konstruktor a következő argumentumokkal kerül meghívásra:

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

A következő értékek kerülnek átadásra a konstruktornak argumentumként:

Paraméter Érték
corePoolSize (hány szál lesz készen (elindulva), amikor a végrehajtó szolgáltatás elindul) 0
maximumPoolSize (a végrehajtó szolgáltatás által létrehozható szálak maximális száma ) Egész.MAX_VALUE
keepAliveTime (az az idő, ameddig egy felszabadult szál tovább él, mielőtt megsemmisülne, ha a szálak száma nagyobb, mint a corePoolSize ) 60L
egység (időegység) Időegység.MÁSODPERC
workQueue (sor megvalósítása) új SynchronousQueue<Futtatható>()

És pontosan ugyanúgy átadhatjuk a saját ThreadFactory implementációnkat.

Beszéljünk a SynchronousQueue-ról

A szinkron átvitel alapötlete meglehetősen egyszerű, és mégis ellentétes az intuitív (vagyis az intuíció vagy a józan ész azt mondja, hogy ez rossz): akkor és csak akkor vehet fel egy elemet a sorba, ha egy másik szál megkapja az elemet a Ugyanakkor. Más szóval, egy szinkron sorban nem lehetnek feladatok, mert amint új feladat érkezik, a végrehajtó szál már felvette a feladatot .

Amikor egy új feladat belép a sorba, és ha van egy szabad aktív szál a készletben, akkor felveszi a feladatot. Ha az összes szál foglalt, akkor egy új szál jön létre.

A gyorsítótárazott készlet nulla szálal kezdődik, és potenciálisan Integer.MAX_VALUE szálra nőhet . A gyorsítótárazott szálkészlet méretét lényegében csak a rendszererőforrások korlátozzák.

A rendszererőforrások kímélése érdekében a gyorsítótárazott szálkészletek eltávolítják az egy percig tétlen szálakat.

Lássuk, hogyan működik a gyakorlatban. Létrehozunk egy feladatosztályt, amely egy felhasználói kérést modellez:

public class Task implements Runnable {
   int taskNumber;

   public Task(int taskNumber) {
       this.taskNumber = taskNumber;
   }

   @Override
   public void run() {
       System.out.println("Processed user request #" + taskNumber + " on thread " + Thread.currentThread().getName());
   }
}

A módszerben létrehozzuk a newCachedThreadPool-t , majd hozzáadunk 3 feladatot a végrehajtáshoz. Itt nyomtatjuk ki szolgáltatásunk állapotát (1) .

Ezután 30 másodpercre szünetet tartunk, újabb feladatot indítunk, és megjelenítjük az állapotot (2) .

Ezt követően 70 másodpercre szüneteltetjük főszálunkat, kinyomtatjuk az állapotot (3) , majd ismét hozzáadunk 3 feladatot, és ismét kinyomtatjuk az állapotot (4) .

Azokon a helyeken, ahol a feladat hozzáadása után azonnal megjelenítjük az állapotot, először 1 másodperces alvást adunk hozzá a naprakész kimenet érdekében.

ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 3; i++) {
            executorService.submit(new Task(i));
        }

        TimeUnit.SECONDS.sleep(1);
            System.out.println(executorService);	//(1)

        TimeUnit.SECONDS.sleep(30);

        executorService.submit(new Task(3));
        TimeUnit.SECONDS.sleep(1);
            System.out.println(executorService);	//(2)

        TimeUnit.SECONDS.sleep(70);

            System.out.println(executorService);	//(3)

        for (int i = 4; i < 7; i++) {
            executorService.submit(new Task(i));
        }

        TimeUnit.SECONDS.sleep(1);
            System.out.println(executorService);	//(4)
        executorService.shutdown();

És íme az eredmény:

Feldolgozott felhasználói kérelem #0 a pool-1-thread-1 szálon
Feldolgozott felhasználói kérelem #1 a pool-1-thread-2 szálon
Feldolgozott felhasználói kérelem #2 a pool-1-thread-3 szálon
(1) java.util.concurrent .ThreadPoolExecutor@f6f4d33[Futtatás, készlet mérete = 3, aktív szálak = 0, sorba állított feladatok = 0, befejezett feladatok = 3]
Feldolgozott felhasználói kérelem #3 a pool-1-thread-2 szálon
(2) java.util.concurrent. ThreadPoolExecutor@f6f4d33[Futtatás, készlet mérete = 3, aktív szálak = 0, sorba állított feladatok = 0, befejezett feladatok = 4]
(3) java.util.concurrent.ThreadPoolExecutor@f6f4d33[Fut, készlet mérete = 0, aktív szálak = 0 , sorban álló feladatok = 0, befejezett feladatok = 4]
Feldolgozott felhasználói kérelem #4 a pool-1-thread-4 szálon
Feldolgozott felhasználói kérelem #5 a pool-1-thread-5 szálon
Feldolgozott felhasználói kérés #6 a pool-1-thread-4 szálon
(4) java.util.concurrent.ThreadPoolExecutor@f6f4d33[Futás, készlet mérete = 2, aktív szálak = 0, sorba helyezett feladatok = 0, befejezett feladatok = 7]

Nézzük végig az egyes lépéseket:

Lépés Magyarázat
1 (3 elvégzett feladat után) Létrehoztunk 3 szálat, és ezen a három szálon 3 feladatot hajtottak végre.
Amikor az állapot megjelenik, mind a 3 feladat elkészült, és a szálak készen állnak más feladatok végrehajtására.
2 (30 másodperc szünet és egy másik feladat végrehajtása után) 30 másodpercnyi inaktivitás után a szálak továbbra is élnek, és feladatokra várnak.
Egy másik feladat hozzáadódik és végrehajtódik a fennmaradó élő szálak készletéből vett szálon.
Nem került új szál a készletbe.
3 (70 másodperces szünet után) A szálakat eltávolították a medencéből.
Nincsenek készen álló szálak a feladatok elfogadására.
4 (további 3 feladat végrehajtása után) Miután több feladat érkezett, új szálak jöttek létre. Ezúttal mindössze két szálnak sikerült 3 feladatot feldolgoznia.

Nos, most már megismerte egy másik típusú végrehajtói szolgáltatás logikáját.

Az Executors segédprogram osztály egyéb módszereivel analóg módon a newCachedThreadPool metódusnak is van egy túlterhelt verziója, amely egy ThreadFactory objektumot vesz argumentumként.