Varför kan du behöva en ExecutorService för 1 tråd?

Du kan använda metoden Executors.newSingleThreadExecutor för att skapa en ExecutorService med en pool som innehåller en enda tråd. Poolens logik är följande:

  • Tjänsten kör endast en uppgift åt gången.
  • Om vi ​​skickar N uppgifter för exekvering, kommer alla N uppgifter att utföras en efter en av den enda tråden.
  • Om tråden avbryts skapas en ny tråd för att utföra eventuella återstående uppgifter.

Låt oss föreställa oss en situation där vårt program kräver följande funktionalitet:

Vi måste behandla användarförfrågningar inom 30 sekunder, men inte mer än en begäran per tidsenhet.

Vi skapar en uppgiftsklass för att behandla en användarförfrågan:


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 modellerar beteendet för att behandla en inkommande förfrågan och visar dess nummer.

Därefter skapar vi i huvudmetoden en ExecutorService för 1 tråd, som vi kommer att använda för att sekventiellt behandla inkommande förfrågningar. Eftersom uppgiftsvillkoren föreskriver "inom 30 sekunder", lägger vi till 30 sekunders väntan och tvångsstoppar 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();
}
    

Efter att ha startat programmet visar konsolen meddelanden om bearbetning av begäran:

Behandlad begäran #0 på tråd id=16
Behandlad begäran #1 på tråd id=16
Behandlad begäran #2 på tråd id=16

Behandlad begäran #29 på tråd id=16

Efter att ha bearbetat förfrågningar i 30 sekunder anropar executorService metoden shutdownNow() , som stoppar den aktuella uppgiften (den som körs) och avbryter alla väntande uppgifter. Därefter avslutas programmet framgångsrikt.

Men allt är inte alltid så perfekt, eftersom vårt program lätt kan få en situation där en av uppgifterna som plockas upp av vår pools enda tråd fungerar felaktigt och till och med avslutar vår tråd. Vi kan simulera den här situationen för att ta reda på hur executorService fungerar med en enda tråd i det här fallet.

För att göra detta, medan en av uppgifterna körs, avslutar vi vår tråd med den osäkra och föråldrade Thread.currentThread().stop()- metoden. Vi gör detta avsiktligt för att simulera situationen där en av uppgifterna avslutar tråden.

Vi kommer att ändra körmetoden i klassen Task :


@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 kommer att avbryta uppgift #5.

Låt oss se hur utgången ser ut med tråden avbruten i slutet av uppgift #5:

Bearbetad begäran #0 på tråd id=16
Behandlad begäran #1 på tråd id=16
Behandlad begäran #2 på tråd id=16
Behandlad begäran #3 på tråd id=16
Behandlad begäran #4 på tråd id=16
Behandlad begäran #6 på tråd id=17
Bearbetad begäran #7 på tråd id=17

Behandlad begäran #29 på tråd id=17

Vi ser att efter att tråden har avbrutits i slutet av uppgift 5, börjar uppgifterna att exekveras i en tråd vars identifierare är 17, även om de tidigare hade körts på tråden med vars identifierare är 16. Och eftersom vår pool har en en enda tråd, detta kan bara betyda en sak: executorService ersatte den stoppade tråden med en ny och fortsatte att utföra uppgifterna.

Således bör vi använda newSingleThreadExecutor med en entrådspool när vi vill bearbeta uppgifter sekventiellt och endast en åt gången, och vi vill fortsätta bearbeta uppgifter från kön oavsett slutförandet av den föregående uppgiften (t.ex. det fall där en av våra uppgifter dödar tråden).

ThreadFactory

När vi talar om att skapa och återskapa trådar kan vi inte låta bli att nämnaThreadFactory.

AThreadFactoryär ett objekt som skapar nya trådar på begäran.

Vi kan skapa vår egen fabrik för att skapa trådar och skicka en instans av den till metoden Executors.newSingleThreadExecutor(ThreadFactory threadFactory) .


ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r, "MyThread");
            }
        });
                    
Vi åsidosätter metoden för att skapa en ny tråd och skickar ett trådnamn till konstruktorn.

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 ändrade namn och prioritet för den skapade tråden.

Så vi ser att vi har 2 överbelastade Executors.newSingleThreadExecutor- metoder. En utan parametrar och en andra med en ThreadFactory- parameter.

Med hjälp av en ThreadFactory kan du konfigurera de skapade trådarna efter behov, till exempel genom att sätta prioriteringar, använda trådunderklasser, lägga till en UncaughtExceptionHandler till tråden och så vidare.