Az Executors osztály newFixedThreadPool metódusa egy executorService-t hoz létre fix számú szálal. A newSingleThreadExecutor metódussal ellentétben megadjuk, hogy hány szálat szeretnénk a készletben. A motorháztető alatt a következő kódot hívják:

new ThreadPoolExecutor(nThreads, nThreads,
                                      	0L, TimeUnit.MILLISECONDS,
                                      	new LinkedBlockingQueue());

A corePoolSize (a szálak száma, amelyek készen lesznek (indítva) a végrehajtó szolgáltatás indulásakor) és maximumPoolSize (a végrehajtó szolgáltatás által létrehozható szálak maximális száma ) paraméterek ugyanazt az értéket kapják – a newFixedThreadPool(nThreads) számára átadott szálak száma ) . És pontosan ugyanúgy átadhatjuk a saját ThreadFactory implementációnkat.

Nos, nézzük meg, miért van szükségünk egy ilyen ExecutorService-re .

Íme az ExecutorService logikája rögzített számú (n) szálal:

  • Legfeljebb n szál lesz aktív a feldolgozási feladatokhoz.
  • Ha több mint n feladatot küld be, akkor azokat a sorban tartják, amíg a szálak fel nem szabadulnak.
  • Ha az egyik szál meghibásodik és megszakad, egy új szál jön létre a helyére.
  • A készlet bármely szála aktív a készlet leállításáig.

Példaként képzelje el, hogy arra vár, hogy átmenjen a biztonsági ellenőrzésen a repülőtéren. Mindenki egy sorban áll, amíg közvetlenül a biztonsági ellenőrzés előtt az utasokat szétosztják az összes működő ellenőrzőpont között. Ha az egyik ellenőrzőpontnál késik, a sort csak a második fogja feldolgozni, amíg az első fel nem szabadul. Ha pedig egy ellenőrzőpont teljesen bezár, akkor a helyére egy másik ellenőrzőpontot nyitnak, és továbbra is két ellenőrzőponton keresztül kezelik az utasokat.

Azonnal megjegyezzük, hogy még ha ideálisak is a feltételek – a beígért n szál stabilan működik, és a hibával végződő szálakat mindig lecseréljük (amit a korlátozott erőforrások lehetetlenné tesznek egy valós repülőtéren) – a rendszernek még mindig van néhány kellemetlen tulajdonságokat, mert semmi esetre sem lesz több szál, még akkor sem, ha a várólista gyorsabban nő, mint ahogyan a szálak fel tudják dolgozni a feladatokat.

Azt javaslom, hogy ismerkedjen meg azzal, hogyan működik az ExecutorService meghatározott számú szálon. Hozzunk létre egy osztályt, amely megvalósítja a Runnable -t . Ennek az osztálynak az objektumai az ExecutorService feladatainkat képviselik .

public class Task implements Runnable {
    int taskNumber;

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

    @Override
    public void run() {
try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Processed user request #" + taskNumber + " on thread " + Thread.currentThread().getName());
    }
}

A run() metódusban 2 másodpercre blokkoljuk a szálat, némi terhelést szimulálva, majd megjelenítjük az aktuális feladat számát és a feladatot végrehajtó szál nevét.

ExecutorService executorService = Executors.newFixedThreadPool(3);

        for (int i = 0; i < 30; i++) {
            executorService.execute(new Task(i));
        }

        executorService.shutdown();

Először is, a módszerben létrehozunk egy ExecutorService-t , és 30 feladatot küldünk be végrehajtásra.

Feldolgozott felhasználói kérelem #1 a pool-1-thread-2 szálon
Feldolgozott felhasználói kérelem #0 a pool-1-thread-1 szálon
Feldolgozott felhasználói kérelem #2 a pool-1-thread-3 szálon
Feldolgozott felhasználói kérelem #5 a készlet-ben 1-szál-3 szál
Feldolgozott felhasználói kérelem #3 a pool-1-thread-2 szálon
Feldolgozott felhasználói kérelem #4 a pool-1-thread-1 szálon
Feldolgozott felhasználói kérelem #8 a pool-1-thread-1 szálon
Feldolgozott felhasználó 6. kérés a pool-1-thread-3 szálon
Feldolgozott felhasználói kérelem #7 a pool-1-thread-2 szálon
Feldolgozott felhasználói kérelem #10 a pool-1-thread-3 szálon
Feldolgozott felhasználói kérelem #9 a pool-1- szál-1 szál
Feldolgozott felhasználói kérés #11 a pool-1-thread-2 szálon
Feldolgozott felhasználói kérelem #12 a pool-1-thread-3 szálon
Feldolgozott felhasználói kérelem #14 a pool-1-thread-2 szálon
Feldolgozott felhasználói kérelem #13 a pool-1-thread-1 szálon
Feldolgozott felhasználói kérelem #15 a pool-1-thread-3 szálon
Feldolgozott felhasználói kérelem #16 a pool- 1-szál-2 szál
Feldolgozott felhasználói kérelem #17 a pool-1-thread-1 szálon
Feldolgozott felhasználói kérelem #18 a pool-1-thread-3 szálon
Feldolgozott felhasználói kérelem #19 a pool-1-thread-2 szálon
Feldolgozott felhasználó 20. kérés a pool-1-thread-1 szálon
Feldolgozott felhasználói kérelem #21 a pool-1-thread-3 szálon
Feldolgozott felhasználói kérelem #22 a pool-1-
thread-2 szálon. szál-1 szál
Feldolgozott felhasználói kérelem #25 a pool-1-thread-2 szálon
Feldolgozott felhasználói kérelem #24 a pool-1-thread-3 szálon
Feldolgozott felhasználói kérés #26 a pool-1-thread-1 szálon
Feldolgozott felhasználói kérelem #27 a pool-1-thread-2 szálon
Feldolgozott felhasználói kérelem #28 a pool-1-thread-3 szálon
Feldolgozott felhasználói kérelem #29 a pool- 1-szál-1 szál

A konzol kimenete megmutatja, hogyan hajtják végre a feladatokat a különböző szálakon, miután az előző feladat felszabadította őket.

Most 100-ra emeljük a feladatok számát, és 100 feladat beküldése után hívjuk meg az awaitTermination(11, SECONDS) metódust. Számot és időegységet adunk át argumentumként. Ez a módszer 11 másodpercre blokkolja a főszálat. Ezután meghívjuk a shutdownNow() függvényt, hogy az ExecutorService leállásra kényszerítsük anélkül, hogy megvárnánk az összes feladat befejezését.

ExecutorService executorService = Executors.newFixedThreadPool(3);

        for (int i = 0; i < 100; i++) {
            executorService.execute(new Task(i));
        }

        executorService.awaitTermination(11, SECONDS);

        executorService.shutdownNow();
        System.out.println(executorService);

A végén információkat jelenítünk meg az executorService állapotáról .

Íme a konzol kimenete:

Feldolgozott felhasználói kérelem #0 a pool-1-thread-1 szálon
Feldolgozott felhasználói kérelem #2 a pool-1-thread-3 szálon
Feldolgozott felhasználói kérelem #1 a pool-1-thread-2
szálon. 1-szál-3 szál
Feldolgozott felhasználói kérelem #5 a pool-1-thread-2 szálon
Feldolgozott felhasználói kérelem #3 a pool-1-thread-1 szálon
Feldolgozott felhasználói kérelem #6 a pool-1-thread-3 szálon
Feldolgozott felhasználó 7. kérés a pool-1-thread-2 szálon
Feldolgozott felhasználói kérelem #8 a pool-1-thread-1 szálon
Feldolgozott felhasználói kérelem #9 a pool-1-thread-3 szálon
Feldolgozott felhasználói kérelem #11 a pool-1-ben szál-1 szál
Feldolgozott felhasználói kérés #10 a pool-1-thread-2 szálon
Feldolgozott felhasználói kérelem #13 a pool-1-thread-1 szálon
Feldolgozott felhasználói kérelem #14 a pool-1-thread-2 szálon
Feldolgozott felhasználói kérelem #12 a pool-1-thread-3 szálon
java.util.concurrent.ThreadPoolExecutor@452b3a41[Leállítás, készlet mérete = 3, aktív szálak = 3 , sorban álló feladatok = 0, befejezett feladatok = 15]
Feldolgozott felhasználói kérelem #17 a pool-1-thread-3 szálon
Feldolgozott felhasználói kérelem #15 a pool-1-thread-1 szálon
Feldolgozott felhasználói kérelem #16 a pool-1-szálon -2 szál

Ezt követi a 3 InterruptedExceptions , amelyet az alvási módszerek dobnak ki 3 aktív feladatból.

Láthatjuk, hogy amikor a program véget ér, 15 feladat elkészült, de a készletben még mindig volt 3 aktív szál, amelyek nem fejezték be a feladatok végrehajtását. Az interrupt() metódus ezen a három szálon kerül meghívásra, ami azt jelenti, hogy a feladat befejeződik, de esetünkben az alvás metódus egy InterruptedExceptiont dob . Azt is látjuk, hogy a shutdownNow() metódus meghívása után a feladatsor törlődik.

Tehát ha egy ExecutorService-t használ rögzített számú szálal a készletben, ne feledje, hogyan működik. Ez a típus ismert állandó terhelésű feladatokra alkalmas.

Még egy érdekes kérdés: ha egyetlen szálhoz kell végrehajtót használni, melyik metódust kell meghívni? newSingleThreadExecutor() vagy newFixedThreadPool(1) ?

Mindkét végrehajtó azonos magatartást tanúsít. Az egyetlen különbség az, hogy a newSingleThreadExecutor() metódus olyan végrehajtót ad vissza, amely később nem konfigurálható további szálak használatára.