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 fő módszerben létrehozunk egy ExecutorService-t , és 30 feladatot küldünk be végrehajtásra.
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 #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.
GO TO FULL VERSION