Bakit maaaring kailanganin mo ang isang ExecutorService para sa 1 thread?

Maaari mong gamitin ang Executors.newSingleThreadExecutor na paraan upang lumikha ng ExecutorService na may pool na may kasamang isang thread. Ang lohika ng pool ay ang mga sumusunod:

  • Ang serbisyo ay nagsasagawa lamang ng isang gawain sa isang pagkakataon.
  • Kung magsusumite kami ng N gawain para sa pagpapatupad, ang lahat ng N gawain ay isasagawa nang paisa-isa ng iisang thread.
  • Kung maaantala ang thread, gagawa ng bagong thread para isagawa ang anumang natitirang mga gawain.

Isipin natin ang isang sitwasyon kung saan ang aming programa ay nangangailangan ng sumusunod na pag-andar:

Kailangan naming iproseso ang mga kahilingan ng user sa loob ng 30 segundo, ngunit hindi hihigit sa isang kahilingan sa bawat yunit ng oras.

Lumilikha kami ng klase ng gawain para sa pagproseso ng kahilingan ng user:


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

Ang klase ay nagmomodelo ng gawi ng pagproseso ng isang papasok na kahilingan at ipinapakita ang numero nito.

Susunod, sa pangunahing paraan, lumikha kami ng ExecutorService para sa 1 thread, na gagamitin namin upang sunud-sunod na iproseso ang mga papasok na kahilingan. Dahil ang mga kondisyon ng gawain ay nagsasaad ng "sa loob ng 30 segundo", nagdaragdag kami ng 30 segundong paghihintay at pagkatapos ay puwersahang ihinto ang 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();
}
    

Pagkatapos simulan ang programa, ang console ay nagpapakita ng mga mensahe tungkol sa pagpoproseso ng kahilingan:

Naprosesong kahilingan #0 sa thread id=16
Naprosesong kahilingan #1 sa thread id=16
Naprosesong kahilingan #2 sa thread id=16

Naprosesong kahilingan #29 sa thread id=16

Pagkatapos iproseso ang mga kahilingan sa loob ng 30 segundo, tatawagan ng executorService ang pamamaraan ng shutdownNow() , na humihinto sa kasalukuyang gawain (ang isasagawa) at kinakansela ang lahat ng nakabinbing gawain. Pagkatapos nito, matagumpay na natapos ang programa.

Ngunit ang lahat ay hindi laging perpekto, dahil ang aming programa ay madaling magkaroon ng isang sitwasyon kung saan ang isa sa mga gawaing kinuha ng nag-iisang thread ng aming pool ay gumagana nang hindi tama at kahit na tinatapos ang aming thread. Maaari naming gayahin ang sitwasyong ito upang malaman kung paano gumagana ang executorService sa isang thread sa kasong ito.

Upang gawin ito, habang isinasagawa ang isa sa mga gawain, tinatapos namin ang aming thread gamit ang hindi ligtas at hindi na ginagamit na Thread.currentThread().stop() na pamamaraan. Sinadya namin itong gawin upang gayahin ang sitwasyon kung saan tinatapos ng isa sa mga gawain ang thread.

Babaguhin namin ang paraan ng pagtakbo sa klase ng Gawain :


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

Aabalahin namin ang gawain #5.

Tingnan natin kung ano ang hitsura ng output sa thread na nagambala sa dulo ng gawain #5:

Naprosesong kahilingan #0 sa thread id=16
Naprosesong kahilingan #1 sa thread id=16
Naprosesong kahilingan #2 sa thread id=16
Naprosesong kahilingan #3 sa thread id=16
Naprosesong kahilingan #4 sa thread id=16
Naprosesong kahilingan #6 noong thread id=17
Naprosesong kahilingan #7 sa thread id=17

Naprosesong kahilingan #29 sa thread id=17

Nakikita namin na pagkatapos na maputol ang thread sa pagtatapos ng gawain 5, ang mga gawain ay magsisimulang isagawa sa isang thread na ang identifier ay 17, kahit na sila ay dati nang naisakatuparan sa thread na ang identifier ay 16. At dahil ang aming pool ay may isang solong thread, isa lang ang ibig sabihin nito: pinalitan ng executorService ang tumigil na thread ng bago at nagpatuloy sa pagpapatupad ng mga gawain.

Kaya, dapat nating gamitin ang newSingleThreadExecutor na may single-thread pool kapag gusto nating iproseso ang mga gawain nang sunud-sunod at isa-isa lang, at gusto nating ipagpatuloy ang pagproseso ng mga gawain mula sa pila anuman ang pagkumpleto ng nakaraang gawain (hal. sa aming mga gawain ay pumapatay sa thread).

ThreadFactory

Kung pinag-uusapan ang paggawa at muling paggawa ng mga thread, hindi namin maiwasang banggitinThreadFactory.

AThreadFactoryay isang bagay na lumilikha ng mga bagong thread on demand.

Maaari tayong lumikha ng sarili nating pabrika ng paggawa ng thread at ipasa ang isang instance nito sa paraang Executors.newSingleThreadExecutor(ThreadFactory threadFactory) .


ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r, "MyThread");
            }
        });
                    
In-override namin ang paraan para sa paglikha ng bagong thread, na nagpapasa ng pangalan ng thread sa constructor.

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;
            }
        });
                    
Binago namin ang pangalan at priyoridad ng ginawang thread.

Kaya nakita namin na mayroon kaming 2 overloaded na Executors.newSingleThreadExecutor na mga pamamaraan. Ang isa ay walang mga parameter, at isang segundo na may isang parameter ng ThreadFactory .

Gamit ang isang ThreadFactory , maaari mong i-configure ang ginawang mga thread kung kinakailangan, halimbawa, sa pamamagitan ng pagtatakda ng mga priyoridad, paggamit ng mga subclass ng thread, pagdaragdag ng isang UncaughtExceptionHandler sa thread, at iba pa.