Hvorfor har du muligvis brug for en ExecutorService til 1 tråd?

Du kan bruge Executors.newSingleThreadExecutor -metoden til at oprette en ExecutorService med en pulje, der inkluderer en enkelt tråd. Puljens logik er som følger:

  • Tjenesten udfører kun én opgave ad gangen.
  • Hvis vi sender N opgaver til udførelse, vil alle N opgaver blive udført efter hinanden af ​​den enkelte tråd.
  • Hvis tråden afbrydes, oprettes en ny tråd til at udføre eventuelle resterende opgaver.

Lad os forestille os en situation, hvor vores program kræver følgende funktionalitet:

Vi skal behandle brugeranmodninger inden for 30 sekunder, men ikke mere end én anmodning pr. tidsenhed.

Vi opretter en opgaveklasse til behandling af en brugeranmodning:


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 adfærden ved behandling af en indgående anmodning og viser dens nummer.

Dernæst opretter vi i hovedmetoden en ExecutorService for 1 tråd, som vi vil bruge til sekventielt at behandle indgående anmodninger. Da opgavebetingelserne foreskriver "inden for 30 sekunder", tilføjer vi 30 sekunders ventetid og stopper derefter ExecutorService med magt .


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

Efter start af programmet viser konsollen meddelelser om anmodningsbehandling:

Behandlet anmodning #0 på tråd id=16
Behandlet anmodning #1 på tråd id=16
Behandlet anmodning #2 på tråd id=16

Behandlet anmodning #29 på tråd id=16

Efter at have behandlet anmodninger i 30 sekunder, kalder executorService metoden shutdownNow() , som stopper den aktuelle opgave (den der udføres) og annullerer alle afventende opgaver. Derefter afsluttes programmet med succes.

Men alt er ikke altid så perfekt, for vores program kunne sagtens have en situation, hvor en af ​​opgaverne samlet op af vores pools eneste tråd fungerer forkert og endda afslutter vores tråd. Vi kan simulere denne situation for at finde ud af, hvordan executorService fungerer med en enkelt tråd i dette tilfælde.

For at gøre dette, mens en af ​​opgaverne udføres, afslutter vi vores tråd ved hjælp af den usikre og forældede Thread.currentThread().stop() metode. Vi gør dette med vilje for at simulere situationen, hvor en af ​​opgaverne afslutter tråden.

Vi ændrer kørselsmetoden i opgaveklassen :


@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 afbryder opgave #5.

Lad os se, hvordan outputtet ser ud med tråden afbrudt i slutningen af ​​opgave #5:

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

Behandlet anmodning #29 på tråd id=17

Vi ser, at efter at tråden er afbrudt i slutningen af ​​opgave 5, begynder opgaverne at blive udført i en tråd, hvis identifikator er 17, selvom de tidligere var blevet udført på tråden med hvis identifikator er 16. Og fordi vores pulje har en enkelt tråd, dette kan kun betyde én ting: executorService erstattede den stoppede tråd med en ny og fortsatte med at udføre opgaverne.

Vi bør således bruge newSingleThreadExecutor med en enkelttrådspulje, når vi ønsker at behandle opgaver sekventielt og kun én ad gangen, og vi vil fortsætte med at behandle opgaver fra køen uanset færdiggørelsen af ​​den tidligere opgave (f.eks. det tilfælde, hvor man af vores opgaver dræber tråden).

ThreadFactory

Når vi taler om at skabe og genskabe tråde, kan vi ikke lade være med at nævneThreadFactory.

ENThreadFactoryer et objekt, der skaber nye tråde efter behov.

Vi kan oprette vores egen trådoprettelsesfabrik og videregive en forekomst af 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 tilsidesætter metoden til at oprette en ny tråd ved at videregive 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 ændrede navn og prioritet på den oprettede tråd.

Så vi ser, at vi har 2 overbelastede Executors.newSingleThreadExecutor -metoder. En uden parametre og en anden med en ThreadFactory- parameter.

Ved at bruge en ThreadFactory kan du konfigurere de oprettede tråde efter behov, for eksempel ved at sætte prioriteter, bruge trådunderklasser, tilføje en UncaughtExceptionHandler til tråden og så videre.