Mengapa anda mungkin memerlukan ExecutorService untuk 1 utas?

Anda boleh menggunakan kaedah Executors.newSingleThreadExecutor untuk mencipta ExecutorService dengan kolam yang merangkumi satu utas. Logik kolam adalah seperti berikut:

  • Perkhidmatan hanya melaksanakan satu tugas pada satu masa.
  • Jika kita menyerahkan N tugasan untuk pelaksanaan, semua N tugasan akan dilaksanakan satu demi satu oleh satu utas.
  • Jika utas terganggu, utas baharu akan dibuat untuk melaksanakan sebarang tugas yang tinggal.

Mari bayangkan situasi di mana program kami memerlukan fungsi berikut:

Kami perlu memproses permintaan pengguna dalam masa 30 saat, tetapi tidak lebih daripada satu permintaan setiap unit masa.

Kami mencipta kelas tugas untuk memproses permintaan pengguna:


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

Kelas memodelkan tingkah laku memproses permintaan masuk dan memaparkan nombornya.

Seterusnya, dalam kaedah utama , kami mencipta ExecutorService untuk 1 utas, yang akan kami gunakan untuk memproses permintaan masuk secara berurutan. Memandangkan syarat tugasan menetapkan "dalam masa 30 saat", kami menambah menunggu 30 saat dan kemudian menghentikan secara paksa 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();
}
    

Selepas memulakan program, konsol memaparkan mesej tentang pemprosesan permintaan:

Permintaan diproses #0 pada thread id=16
Permintaan diproses #1 pada thread id=16
Permintaan diproses #2 pada thread id=16

Permintaan diproses #29 pada thread id=16

Selepas memproses permintaan selama 30 saat, executorService memanggil kaedah shutdownNow() , yang menghentikan tugas semasa (yang sedang dilaksanakan) dan membatalkan semua tugas yang belum selesai. Selepas itu, program tamat dengan jayanya.

Tetapi segala-galanya tidak selalunya begitu sempurna, kerana program kami dengan mudah boleh mempunyai situasi di mana salah satu tugasan yang diambil oleh satu-satunya utas kolam kami berfungsi dengan tidak betul dan malah menamatkan rangkaian kami. Kita boleh mensimulasikan situasi ini untuk mengetahui cara executorService berfungsi dengan satu utas dalam kes ini.

Untuk melakukan ini, semasa salah satu tugas sedang dilaksanakan, kami menamatkan urutan kami menggunakan kaedah Thread.currentThread().stop() yang tidak selamat dan lapuk . Kami melakukan ini dengan sengaja untuk mensimulasikan situasi di mana salah satu tugas menamatkan rangkaian.

Kami akan menukar kaedah larian dalam kelas Tugas :


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

Kami akan mengganggu tugasan #5.

Mari lihat bagaimana output kelihatan dengan benang terganggu pada akhir tugasan #5:

Permintaan diproses #0 pada thread id=16
Permintaan diproses #1 pada thread id=16
Permintaan diproses #2 pada thread id=16
Permintaan diproses #3 pada thread id=16
Permintaan diproses #4 pada thread id=16
Permintaan diproses #6 pada thread id=17
Permintaan diproses #7 pada thread id=17

Permintaan diproses #29 pada thread id=17

Kami melihat bahawa selepas utas diganggu pada penghujung tugasan 5, tugasan mula dilaksanakan dalam utas yang pengecamnya ialah 17, walaupun sebelum ini mereka telah dilaksanakan pada utas dengan pengecamnya ialah 16. Dan kerana kumpulan kami mempunyai utas tunggal, ini hanya boleh bermakna satu perkara: executorService menggantikan utas yang dihentikan dengan yang baharu dan terus melaksanakan tugas.

Oleh itu, kita harus menggunakan newSingleThreadExecutor dengan kumpulan satu-utas apabila kita mahu memproses tugasan secara berurutan dan hanya satu demi satu, dan kita mahu meneruskan pemprosesan tugas daripada baris gilir tanpa mengira penyiapan tugasan sebelumnya (cth kes di mana satu tugas kami membunuh benang).

ThreadFactory

Apabila bercakap tentang mencipta dan mencipta semula benang, kami tidak boleh tidak menyebutnyaThreadFactory.

AThreadFactoryialah objek yang mencipta benang baharu atas permintaan.

Kita boleh mencipta kilang penciptaan benang kita sendiri dan menyampaikan contohnya kepada kaedah Executors.newSingleThreadExecutor(ThreadFactory threadFactory) .


ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r, "MyThread");
            }
        });
                    
Kami mengatasi kaedah untuk mencipta utas baharu, menghantar nama utas kepada pembina.

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;
            }
        });
                    
Kami menukar nama dan keutamaan utas yang dibuat.

Oleh itu, kita melihat bahawa kita mempunyai 2 kaedah Pelaksana yang berlebihan.newSingleThreadExecutor . Satu tanpa parameter, dan satu lagi dengan parameter ThreadFactory .

Menggunakan ThreadFactory , anda boleh mengkonfigurasi benang yang dibuat mengikut keperluan, contohnya, dengan menetapkan keutamaan, menggunakan subkelas benang, menambah UncaughtExceptionHandler pada benang dan sebagainya.