Hvorfor trenger du kanskje en ExecutorService for 1 tråd?

Du kan bruke Executors.newSingleThreadExecutor -metoden til å opprette en ExecutorService med en pool som inkluderer en enkelt tråd. Bassengets logikk er som følger:

  • Tjenesten utfører bare én oppgave om gangen.
  • Hvis vi sender inn N oppgaver for utførelse, vil alle N oppgaver bli utført etter hverandre av den enkelte tråden.
  • Hvis tråden blir avbrutt, vil en ny tråd bli opprettet for å utføre eventuelle gjenværende oppgaver.

La oss forestille oss en situasjon der programmet vårt krever følgende funksjonalitet:

Vi må behandle brukerforespørsler innen 30 sekunder, men ikke mer enn én forespørsel per tidsenhet.

Vi oppretter en oppgaveklasse for å behandle en brukerforespørsel:


class Task implements Runnable {
   private final int taskNumber;

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

   @Override
   public void run() {
       try {
           Thread.sleep(1000);
       } catch (InterruptedException ignored) {
       }
       System.out.printf("Processed request #%d on thread id=%d\\n", taskNumber, Thread.currentThread().getId());
   }
}
    

Klassen modellerer oppførselen til å behandle en innkommende forespørsel og viser nummeret.

Deretter, i hovedmetoden , oppretter vi en ExecutorService for 1 tråd, som vi vil bruke til å sekvensielt behandle innkommende forespørsler. Siden oppgavebetingelsene angir "innen 30 sekunder", legger vi til 30 sekunders ventetid og tvangsstopper ExecutorService .


public static void main(String[] args) throws InterruptedException {
   ExecutorService executorService = Executors.newSingleThreadExecutor();

   for (int i = 0; i < 1_000; i++) {
       executorService.execute(new Task(i));
   }
   executorService.awaitTermination(30, TimeUnit.SECONDS);
   executorService.shutdownNow();
}
    

Etter å ha startet programmet, viser konsollen meldinger om forespørselsbehandling:

Behandlet forespørsel #0 på tråd id=16
Behandlet forespørsel #1 på tråd id=16
Behandlet forespørsel #2 på tråd id=16

Behandlet forespørsel #29 på tråd id=16

Etter å ha behandlet forespørsler i 30 sekunder, kaller executorService opp shutdownNow()- metoden, som stopper gjeldende oppgave (den som utføres) og kansellerer alle ventende oppgaver. Etter det avsluttes programmet vellykket.

Men alt er ikke alltid så perfekt, for programmet vårt kan lett få en situasjon der en av oppgavene som plukkes opp av bassengets eneste tråd fungerer feil og til og med avslutter tråden vår. Vi kan simulere denne situasjonen for å finne ut hvordan executorService fungerer med en enkelt tråd i dette tilfellet.

For å gjøre dette, mens en av oppgavene utføres, avslutter vi tråden vår ved å bruke den usikre og foreldede Thread.currentThread().stop()- metoden. Vi gjør dette med vilje for å simulere situasjonen der en av oppgavene avslutter tråden.

Vi endrer kjøringsmetoden i oppgaveklassen :


@Override
public void run() {
   try {
       Thread.sleep(1000);
   } catch (InterruptedException ignored) {
   }

   if (taskNumber == 5) {
       Thread.currentThread().stop();
   }

   System.out.printf("Processed request #%d on thread id=%d\\n", taskNumber, Thread.currentThread().getId());
}
    

Vi avbryter oppgave #5.

La oss se hvordan utgangen ser ut med tråden avbrutt på slutten av oppgave #5:

Behandlet forespørsel #0 på tråd id=16
Behandlet forespørsel #1 på tråd id=16
Behandlet forespørsel #2 på tråd id=16
Behandlet forespørsel #3 på tråd id=16
Behandlet forespørsel #4 på tråd id=16
Behandlet forespørsel #6 på tråd id=17
Behandlet forespørsel #7 på tråd id=17

Behandlet forespørsel #29 på tråd id=17

Vi ser at etter at tråden er avbrutt på slutten av oppgave 5, begynner oppgavene å bli utført i en tråd hvis identifikator er 17, selv om de tidligere hadde blitt utført på tråden med identifikatoren 16. Og fordi vår pool har en enkelt tråd, dette kan bare bety én ting: executorService erstattet den stoppede tråden med en ny og fortsatte å utføre oppgavene.

Derfor bør vi bruke newSingleThreadExecutor med en enkelttrådspool når vi ønsker å behandle oppgaver sekvensielt og kun én om gangen, og vi ønsker å fortsette å behandle oppgaver fra køen uavhengig av fullføringen av den forrige oppgaven (f.eks. av våre oppgaver dreper tråden).

ThreadFactory

Når vi snakker om å lage og gjenskape tråder, kan vi ikke la være å nevneThreadFactory.

ENThreadFactoryer et objekt som lager nye tråder etter behov.

Vi kan lage vår egen trådopprettingsfabrikk og sende en forekomst av den til Executors.newSingleThreadExecutor(ThreadFactory threadFactory) -metoden.


ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r, "MyThread");
            }
        });
                    
Vi overstyrer metoden for å lage en ny tråd, og sender et trådnavn til konstruktøren.

ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r, "MyThread");
                thread.setPriority(Thread.MAX_PRIORITY);
                return thread;
            }
        });
                    
Vi endret navn og prioritet på den opprettede tråden.

Så vi ser at vi har 2 overbelastede Executors.newSingleThreadExecutor -metoder. En uten parametere, og en andre med en ThreadFactory- parameter.

Ved å bruke en ThreadFactory kan du konfigurere de opprettede trådene etter behov, for eksempel ved å angi prioriteter, bruke trådunderklasser, legge til en UncaughtExceptionHandler i tråden, og så videre.