NewFixedThreadPool - metoden i klassen Executors skapar en executorService med ett fast antal trådar. Till skillnad från metoden newSingleThreadExecutor anger vi hur många trådar vi vill ha i poolen. Under huven kallas följande kod:

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

Parametrarna corePoolSize (antalet trådar som kommer att vara klara (startade) när executor -tjänsten startar) och maximumPoolSize (det maximala antalet trådar som executor -tjänsten kan skapa) får samma värde — antalet trådar som skickas till newFixedThreadPool(nThreads ) . Och vi kan skicka vår egen implementering av ThreadFactory på exakt samma sätt.

Tja, låt oss se varför vi behöver en sådan ExecutorService .

Här är logiken för en ExecutorService med ett fast antal (n) trådar:

  • Högst n trådar kommer att vara aktiva för bearbetningsuppgifter.
  • Om fler än n uppgifter skickas in kommer de att hållas i kön tills trådarna blir lediga.
  • Om en av trådarna misslyckas och avslutas skapas en ny tråd som tar dess plats.
  • Eventuell tråd i poolen är aktiv tills poolen stängs av.

Som ett exempel, tänk dig att vänta på att gå igenom säkerhetskontrollen på flygplatsen. Alla står på en rad tills omedelbart före säkerhetskontrollen fördelas passagerarna på alla fungerande checkpoints. Om det blir en fördröjning vid en av kontrollpunkterna kommer kön att behandlas av endast den andra tills den första är ledig. Och om en checkpoint stänger helt, kommer en annan checkpoint att öppnas för att ersätta den, och passagerare kommer att fortsätta att behandlas genom två checkpoints.

Vi kommer genast att notera att även om förhållandena är idealiska — de utlovade n trådarna fungerar stabilt och trådar som slutar med ett fel byts alltid ut (något som begränsade resurser gör omöjligt att uppnå på en riktig flygplats) — har systemet fortfarande flera obehagliga funktioner, eftersom det under inga omständigheter kommer att finnas fler trådar, även om kön växer snabbare än vad trådarna kan bearbeta uppgifter.

Jag föreslår att du ska få en praktisk förståelse för hur ExecutorService fungerar med ett fast antal trådar. Låt oss skapa en klass som implementerar Runnable . Objekt i denna klass representerar våra uppgifter för 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 blockerar vi tråden i 2 sekunder, simulerar viss arbetsbelastning, och visar sedan den aktuella uppgiftens nummer och namnet på tråden som utför uppgiften.

ExecutorService executorService = Executors.newFixedThreadPool(3);

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

        executorService.shutdown();

Till att börja med, i huvudmetoden, skapar vi en ExecutorService och skickar in 30 uppgifter för exekvering.

Bearbetad användarförfrågan #1 på pool-1-tråd-2 tråd
Bearbetad användarförfrågan #0 på pool-1-tråd-1 tråd
Bearbetad användarförfrågan #2 på pool-1-tråd-3 tråd
Bearbetad användarförfrågan #5 på pool- 1-tråd-3 tråd
Bearbetad användarförfrågan #3 på pool-1-tråd-2 tråd
Bearbetad användarförfrågan #4 på pool-1-tråd-1 tråd
Bearbetad användarförfrågan #8 på pool-1-tråd-1 tråd
Bearbetad användare begäran #6 på pool-1-thread-3-tråden
Bearbetad användarförfrågan #7 på pool-1-thread-2-tråden
Bearbetad användarförfrågan #10 på pool-1-thread-3-tråden
Bearbetad användarförfrågan #9 på pool-1- tråd-1 tråd
Bearbetad användarförfrågan #11 på pool-1-tråd-2 tråd
Bearbetad användarförfrågan #12 på tråd pool-1-tråd-3
Behandlad användarförfrågan #14 på pool-1-tråd-2 tråd
Bearbetad användarförfrågan #13 på pool-1-tråd-1 tråd
Bearbetad användarförfrågan #15 på pool-1-tråd-3 tråd
Bearbetad användarförfrågan #16 på pool- 1-tråd-2 tråd
Bearbetad användarförfrågan #17 på pool-1-tråd-1 tråd
Bearbetad användarförfrågan #18 på pool-1-tråd-3 tråd
Bearbetad användarförfrågan #19 på pool-1-tråd-2 tråd
Bearbetad användare begäran #20 på pool-1-thread-1-tråden
Behandlad användarförfrågan #21 på pool-1-thread-3-tråden
Bearbetad användarförfrågan #22 på pool-1-thread-2-tråden
Bearbetad användarförfrågan #23 på pool-1- tråd-1 tråd
Bearbetad användarförfrågan #25 på pool-1-tråd-2 tråd
Bearbetad användarförfrågan #24 på pool-1-tråd-3 tråd
Bearbetad användarförfrågan #26 på pool-1-thread-1-tråden
Bearbetad användarförfrågan #27 på pool-1-thread-2-tråden
Bearbetad användarförfrågan #28 på pool-1-thread-3-tråden
Bearbetad användarförfrågan #29 på pool- 1-tråd-1 tråd

Konsolutdata visar oss hur uppgifterna exekveras på olika trådar när de väl har släppts av den föregående uppgiften.

Nu kommer vi att öka antalet uppgifter till 100, och efter att ha skickat in 100 uppgifter kommer vi att anropa metoden awaitTermination(11, SEKUNDER) . Vi skickar ett tal och en tidsenhet som argument. Denna metod kommer att blockera huvudtråden i 11 sekunder. Sedan kommer vi att anropa shutdownNow() för att tvinga ExecutorService att stängas av utan att vänta på att alla uppgifter ska slutföras.

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

I slutet kommer vi att visa information om tillståndet för executorService .

Här är konsolutgången vi får:

Bearbetad användarförfrågan #0 på pool-1-thread-1-tråden
Bearbetad användarförfrågan #2 på pool-1-thread-3-tråden
Bearbetad användarförfrågan #1 på pool-1-thread-2-tråden
Bearbetad användarförfrågan #4 på pool- 1-tråd-3 tråd
Bearbetad användarförfrågan #5 på pool-1-tråd-2 tråd
Bearbetad användarförfrågan #3 på pool-1-tråd-1 tråd
Behandlad användarförfrågan #6 på tråd pool-1-tråd-3
Behandlad användare begäran #7 på pool-1-thread-2-tråden
Bearbetad användarförfrågan #8 på pool-1-thread-1-tråden
Bearbetad användarförfrågan #9 på pool-1-thread-3-tråden
Bearbetad användarförfrågan #11 på pool-1- tråd-1-tråd
Bearbetad användarförfrågan #10 på pool-1-tråd-2 tråd
Bearbetad användarförfrågan #13 på pool-1-tråd-1 tråd
Bearbetad användarförfrågan #14 på pool-1-thread-2-tråden
Bearbetad användarförfrågan #12 på pool-1-thread-3-tråden
java.util.concurrent.ThreadPoolExecutor@452b3a41[Stänger av, poolstorlek = 3, aktiva trådar = 3 , köade uppgifter = 0, slutförda uppgifter = 15]
Bearbetad användarförfrågan #17 på pool-1-tråd-3 tråd
Bearbetad användarförfrågan #15 på pool-1-tråd-1 tråd
Bearbetad användarförfrågan #16 på pool-1-tråd -2 trådar

Detta följs av 3 InterruptedExceptions , kastade av sömnmetoder från 3 aktiva uppgifter.

Vi kan se att när programmet avslutas är 15 uppgifter gjorda, men poolen hade fortfarande 3 aktiva trådar som inte slutförde sina uppgifter. Metoden interrupt() anropas på dessa tre trådar, vilket betyder att uppgiften kommer att slutföras, men i vårt fall kastar sleep -metoden en InterruptedException . Vi ser också att efter att metoden shutdownNow() har anropats, rensas uppgiftskön.

Så när du använder en ExecutorService med ett fast antal trådar i poolen, se till att komma ihåg hur det fungerar. Denna typ är lämplig för uppgifter med en känd konstant belastning.

Här är en annan intressant fråga: om du behöver använda en executor för en enda tråd, vilken metod ska du anropa? newSingleThreadExecutor() eller newFixedThreadPool(1) ?

Båda utförarna kommer att ha likvärdigt beteende. Den enda skillnaden är att metoden newSingleThreadExecutor() kommer att returnera en executor som inte senare kan konfigureras om för att använda ytterligare trådar.