De methode newFixedThreadPool van de klasse Executors maakt een executorService met een vast aantal threads. In tegenstelling tot de methode newSingleThreadExecutor geven we aan hoeveel threads we in de pool willen hebben. Onder de motorkap heet de volgende code:

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

De parameters corePoolSize (het aantal threads dat gereed is (gestart) wanneer de executor- service start) en maximumPoolSize (het maximum aantal threads dat de executor- service kan maken) krijgen dezelfde waarde: het aantal threads dat wordt doorgegeven aan newFixedThreadPool(nThreads ) . En we kunnen onze eigen implementatie van ThreadFactory op precies dezelfde manier doorgeven.

Laten we eens kijken waarom we zo'n ExecutorService nodig hebben .

Dit is de logica van een ExecutorService met een vast aantal (n) threads:

  • Er zullen maximaal n threads actief zijn voor verwerkingstaken.
  • Als er meer dan n taken worden ingediend, worden ze in de wachtrij gehouden totdat er threads vrijkomen.
  • Als een van de threads faalt en eindigt, wordt er een nieuwe thread gemaakt om zijn plaats in te nemen.
  • Elke thread in de pool is actief totdat de pool wordt afgesloten.

Stel je bijvoorbeeld voor dat je wacht om door de beveiliging op de luchthaven te gaan. Iedereen staat in één rij tot vlak voor de security check de passagiers worden verdeeld over alle werkende checkpoints. Als er een vertraging is bij een van de checkpoints, wordt de wachtrij alleen door de tweede verwerkt totdat de eerste vrij is. En als een checkpoint volledig sluit, wordt er een ander checkpoint geopend om het te vervangen en worden passagiers verder verwerkt via twee checkpoints.

We zullen meteen opmerken dat zelfs als de omstandigheden ideaal zijn - de beloofde n threads werken stabiel, en threads die eindigen op een fout worden altijd vervangen (iets wat met beperkte middelen onmogelijk te realiseren is op een echte luchthaven) - het systeem nog steeds verschillende vervelende eigenschappen, want er zullen in geen geval meer threads zijn, zelfs niet als de wachtrij sneller groeit dan de threads taken kunnen verwerken.

Ik stel voor om praktisch inzicht te krijgen in hoe ExecutorService werkt met een vast aantal threads. Laten we een klasse maken die Runnable implementeert . Objecten van deze klasse vertegenwoordigen onze taken voor de ExecutorService .

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());
    }
}

In de methode run() blokkeren we de thread gedurende 2 seconden, simuleren we enige werklast, en tonen we vervolgens het nummer van de huidige taak en de naam van de thread die de taak uitvoert.

ExecutorService executorService = Executors.newFixedThreadPool(3);

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

        executorService.shutdown();

Om te beginnen maken we in de hoofdmethode een ExecutorService en dienen we 30 taken in voor uitvoering.

Verwerkt gebruikersverzoek #1 op pool-1-thread-2 thread
Verwerkt gebruikersverzoek #0 op pool-1-thread-1 thread
Verwerkt gebruikersverzoek #2 op pool-1-thread-3 thread
Verwerkt gebruikersverzoek #5 op pool- 1-thread-3 thread
Verwerkt gebruikersverzoek #3 op pool-1-thread-2 thread
Verwerkt gebruikersverzoek #4 op pool-1-thread-1 thread
Verwerkt gebruikersverzoek #8 op pool-1-thread-1 thread
Verwerkte gebruiker verzoek #6 op pool-1-thread-3 thread
Verwerkt gebruikersverzoek #7 op pool-1-thread-2 thread
Verwerkt gebruikersverzoek #10 op pool-1-thread-3 thread
Verwerkt gebruikersverzoek #9 op pool-1- thread-1 thread
Verwerkt gebruikersverzoek #11 op pool-1-thread-2 thread
Verwerkt gebruikersverzoek #12 op pool-1-thread-3 thread
Verwerkt gebruikersverzoek #14 op pool-1-thread-2 thread
Verwerkt gebruikersverzoek #13 op pool-1-thread-1 thread
Verwerkt gebruikersverzoek #15 op pool-1-thread-3 thread
Verwerkt gebruikersverzoek #16 op pool- 1-thread-2 thread
Verwerkt gebruikersverzoek #17 op pool-1-thread-1 thread
Verwerkt gebruikersverzoek #18 op pool-1-thread-3 thread
Verwerkt gebruikersverzoek #19 op pool-1-thread-2 thread
Verwerkte gebruiker verzoek #20 op pool-1-thread-1 thread
Verwerkt gebruikersverzoek #21 op pool-1-thread-3 thread
Verwerkt gebruikersverzoek #22 op pool-1-thread-2 thread
Verwerkt gebruikersverzoek #23 op pool-1- thread-1 thread
Verwerkt gebruikersverzoek #25 op pool-1-thread-2 thread
Verwerkt gebruikersverzoek #24 op pool-1-thread-3 thread
Verwerkt gebruikersverzoek #26 op pool-1-thread-1 thread
Verwerkt gebruikersverzoek #27 op pool-1-thread-2 thread
Verwerkt gebruikersverzoek #28 op pool-1-thread-3 thread
Verwerkt gebruikersverzoek #29 op pool- 1-draads-1 draad

De console-uitvoer laat ons zien hoe de taken worden uitgevoerd op verschillende threads zodra ze zijn vrijgegeven door de vorige taak.

Nu verhogen we het aantal taken tot 100, en na het indienen van 100 taken, zullen we de waitTermination(11, SECONDS) methode aanroepen . We geven een getal en tijdseenheid door als argumenten. Deze methode blokkeert de hoofdthread gedurende 11 seconden. Vervolgens roepen we shutdownNow() aan om de ExecutorService te dwingen af ​​te sluiten zonder te wachten tot alle taken zijn voltooid.

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);

Aan het einde geven we informatie weer over de status van de executorService .

Dit is de console-uitvoer die we krijgen:

Verwerkt gebruikersverzoek #0 op pool-1-thread-1 thread
Verwerkt gebruikersverzoek #2 op pool-1-thread-3 thread
Verwerkt gebruikersverzoek #1 op pool-1-thread-2 thread
Verwerkt gebruikersverzoek #4 op pool- 1-thread-3 thread
Verwerkt gebruikersverzoek #5 op pool-1-thread-2 thread
Verwerkt gebruikersverzoek #3 op pool-1-thread-1 thread
Verwerkt gebruikersverzoek #6 op pool-1-thread-3 thread
Verwerkte gebruiker verzoek #7 op pool-1-thread-2 thread
Verwerkt gebruikersverzoek #8 op pool-1-thread-1 thread
Verwerkt gebruikersverzoek #9 op pool-1-thread-3 thread
Verwerkt gebruikersverzoek #11 op pool-1- thread-1 thread
Verwerkt gebruikersverzoek #10 op pool-1-thread-2 thread
Verwerkt gebruikersverzoek #13 op pool-1-thread-1 thread
Verwerkt gebruikersverzoek #14 op pool-1-thread-2 thread
Verwerkt gebruikersverzoek #12 op pool-1-thread-3 thread
java.util.concurrent.ThreadPoolExecutor@452b3a41[Afsluiten, poolgrootte = 3, actieve threads = 3 , taken in wachtrij = 0, voltooide taken = 15]
Verwerkt gebruikersverzoek #17 op pool-1-thread-3 thread
Verwerkt gebruikersverzoek #15 op pool-1-thread-1 thread
Verwerkt gebruikersverzoek #16 op pool-1-thread -2 draad

Dit wordt gevolgd door 3 InterruptedExceptions , gegenereerd door slaapmethoden van 3 actieve taken.

We kunnen zien dat wanneer het programma eindigt, er 15 taken zijn uitgevoerd, maar dat de pool nog steeds 3 actieve threads heeft die hun taken niet hebben voltooid. De methode interrupt() wordt aangeroepen op deze drie threads, wat betekent dat de taak wordt voltooid, maar in ons geval genereert de slaapmethode een InterruptedException . We zien ook dat nadat de methode shutdownNow() is aangeroepen, de taakwachtrij wordt gewist.

Dus als u een ExecutorService gebruikt met een vast aantal threads in de pool, vergeet dan niet hoe het werkt. Dit type is geschikt voor taken met een bekende constante belasting.

Hier is nog een interessante vraag: als u een uitvoerder voor een enkele thread moet gebruiken, welke methode moet u dan aanroepen? newSingleThreadExecutor() of newFixedThreadPool(1) ?

Beide uitvoerders zullen gelijkwaardig gedrag vertonen. Het enige verschil is dat de methode newSingleThreadExecutor() een executor retourneert die later niet opnieuw kan worden geconfigureerd om extra threads te gebruiken.