Den nyeFixedThreadPool -metoden i Executors -klassen oppretter en executorService med et fast antall tråder. I motsetning til newSingleThreadExecutor- metoden, spesifiserer vi hvor mange tråder vi vil ha i bassenget. Under panseret kalles følgende kode:

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

Parameterne corePoolSize (antall tråder som vil være klare (startes) når executor- tjenesten starter) og maximumPoolSize (maksimalt antall tråder som executor -tjenesten kan opprette) får samme verdi - antall tråder som sendes til newFixedThreadPool(nThreads ) . Og vi kan passere vår egen implementering av ThreadFactory på nøyaktig samme måte.

Vel, la oss se hvorfor vi trenger en slik ExecutorService .

Her er logikken til en ExecutorService med et fast antall (n) tråder:

  • Maksimalt n tråder vil være aktive for behandlingsoppgaver.
  • Hvis mer enn n oppgaver sendes inn, vil de bli holdt i køen til trådene blir ledige.
  • Hvis en av trådene mislykkes og avsluttes, vil en ny tråd bli opprettet for å ta dens plass.
  • Eventuell tråd i bassenget er aktiv til bassenget stenges.

Tenk deg som et eksempel å vente med å gå gjennom sikkerhetskontrollen på flyplassen. Alle står på en linje inntil rett før sikkerhetskontrollen er passasjerer fordelt på alle arbeidskontrollene. Hvis det er forsinkelse ved et av sjekkpunktene, vil køen behandles av kun den andre til den første er ledig. Og hvis ett sjekkpunkt lukkes helt, vil et annet sjekkpunkt åpnes for å erstatte det, og passasjerer vil fortsette å bli behandlet gjennom to sjekkpunkter.

Vi vil umiddelbart legge merke til at selv om forholdene er ideelle — de lovede n-trådene fungerer stabilt, og tråder som ender med en feil blir alltid erstattet (noe som begrensede ressurser gjør det umulig å oppnå på en ekte flyplass) — har systemet fortsatt flere ubehagelige funksjoner, fordi det under ingen omstendigheter vil være flere tråder, selv om køen vokser raskere enn trådene kan behandle oppgaver.

Jeg foreslår å få en praktisk forståelse av hvordan ExecutorService fungerer med et fast antall tråder. La oss lage en klasse som implementerer Runnable . Objekter i denne klassen representerer våre oppgaver for 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());
    }
}

I run()- metoden blokkerer vi tråden i 2 sekunder, simulerer en viss arbeidsbelastning, og viser deretter gjeldende oppgaves nummer og navnet på tråden som utfører oppgaven.

ExecutorService executorService = Executors.newFixedThreadPool(3);

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

        executorService.shutdown();

Til å begynne med, i hovedmetoden , oppretter vi en ExecutorService og sender inn 30 oppgaver for utførelse.

Behandlet brukerforespørsel #1 på pool-1-thread-2 thread
Behandlet brukerforespørsel #0 på pool-1-thread-1 thread
Behandlet brukerforespørsel #2 på pool-1-thread-3 thread
Behandlet brukerforespørsel #5 på pool- 1-tråd-3 tråd
Behandlet brukerforespørsel #3 på pool-1-tråd-2 tråd
Behandlet brukerforespørsel #4 på pool-1-tråd-1 tråd
Behandlet brukerforespørsel #8 på pool-1-tråd-1 tråd
Behandlet bruker forespørsel #6 på pool-1-thread-3 thread
Behandlet brukerforespørsel #7 på pool-1-thread-2 thread
Behandlet brukerforespørsel #10 på pool-1-thread-3 thread
Behandlet brukerforespørsel #9 på pool-1- thread-1 thread
Behandlet brukerforespørsel #11 på pool-1-thread-2 thread
Behandlet brukerforespørsel #12 på pool-1-thread-3 thread
Behandlet brukerforespørsel #14 på pool-1-thread-2 thread
Behandlet brukerforespørsel #13 på pool-1-thread-1 thread
Behandlet brukerforespørsel #15 på pool-1-thread-3 thread
Behandlet brukerforespørsel #16 på pool- 1-tråd-2 tråd
Behandlet brukerforespørsel #17 på pool-1-tråd-1 tråd
Behandlet brukerforespørsel #18 på pool-1-tråd-3 tråd
Behandlet brukerforespørsel #19 på pool-1-tråd-2 tråd
Behandlet bruker forespørsel #20 på pool-1-thread-1 thread
Behandlet brukerforespørsel #21 på pool-1-thread-3 thread
Behandlet brukerforespørsel #22 på pool-1-thread-2 thread
Behandlet brukerforespørsel #23 på pool-1- thread-1 thread
Behandlet brukerforespørsel #25 på pool-1-thread-2 thread
Behandlet brukerforespørsel #24 på pool-1-thread-3 thread
Behandlet brukerforespørsel #26 på pool-1-thread-1 thread
Behandlet brukerforespørsel #27 på pool-1-thread-2 thread
Behandlet brukerforespørsel #28 på pool-1-thread-3 thread
Behandlet brukerforespørsel #29 på pool- 1-tråds-1 tråd

Konsollutgangen viser oss hvordan oppgavene utføres på forskjellige tråder når de er utgitt av forrige oppgave.

Nå øker vi antallet oppgaver til 100, og etter å ha sendt inn 100 oppgaver, kaller vi awaitTermination (11, SEKUNDER) -metoden. Vi sender et tall og en tidsenhet som argumenter. Denne metoden vil blokkere hovedtråden i 11 sekunder. Deretter vil vi kalle shutdownNow() for å tvinge ExecutorService til å stenge uten å vente på at alle oppgaver skal fullføres.

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

På slutten vil vi vise informasjon om tilstanden til executorService .

Her er konsollutgangen vi får:

Behandlet brukerforespørsel #0 på pool-1-thread-1 thread
Behandlet brukerforespørsel #2 på pool-1-thread-3 thread
Behandlet brukerforespørsel #1 på pool-1-thread-2 thread
Behandlet brukerforespørsel #4 på pool- 1-tråd-3 tråd
Behandlet brukerforespørsel #5 på pool-1-tråd-2 tråd
Behandlet brukerforespørsel #3 på pool-1-tråd-1 tråd
Behandlet brukerforespørsel #6 på pool-1-tråd-3 tråd
Behandlet bruker forespørsel #7 på pool-1-thread-2 thread
Behandlet brukerforespørsel #8 på pool-1-thread-1 thread
Behandlet brukerforespørsel #9 på pool-1-thread-3 thread
Behandlet brukerforespørsel #11 på pool-1- thread-1 thread
Behandlet brukerforespørsel #10 på pool-1-thread-2 thread
Behandlet brukerforespørsel #13 på pool-1-thread-1 thread
Behandlet brukerforespørsel #14 på pool-1-thread-2 thread
Behandlet brukerforespørsel #12 på pool-1-thread-3 thread
java.util.concurrent.ThreadPoolExecutor@452b3a41[Avslutt, bassengstørrelse = 3, aktive tråder = 3 , oppgaver i kø = 0, fullførte oppgaver = 15]
Behandlet brukerforespørsel #17 på pool-1-thread-3 thread
Behandlet brukerforespørsel #15 på pool-1-thread-1 thread
Behandlet brukerforespørsel #16 på pool-1-thread -2 tråder

Dette etterfølges av 3 InterruptedExceptions , kastet av søvnmetoder fra 3 aktive oppgaver.

Vi kan se at når programmet avsluttes, er 15 oppgaver utført, men bassenget hadde fortsatt 3 aktive tråder som ikke fullførte oppgavene sine. Interrupt () -metoden kalles på disse tre trådene, noe som betyr at oppgaven vil fullføres, men i vårt tilfelle kaster søvnmetoden en InterruptedException . Vi ser også at etter at shutdownNow()- metoden er kalt, tømmes oppgavekøen.

Så når du bruker en ExecutorService med et fast antall tråder i bassenget, husk å huske hvordan det fungerer. Denne typen egner seg for oppgaver med kjent konstant belastning.

Her er et annet interessant spørsmål: hvis du trenger å bruke en eksekvering for en enkelt tråd, hvilken metode bør du ringe? newSingleThreadExecutor() eller newFixedThreadPool(1) ?

Begge eksekutorer vil ha tilsvarende oppførsel. Den eneste forskjellen er at newSingleThreadExecutor()- metoden vil returnere en eksekutør som ikke senere kan rekonfigureres til å bruke flere tråder.