Kaedah newFixedThreadPool kelas Pelaksana mencipta executorService dengan bilangan benang tetap. Tidak seperti kaedah newSingleThreadExecutor , kami menentukan bilangan utas yang kami mahu dalam kumpulan. Di bawah tudung, kod berikut dipanggil:


new ThreadPoolExecutor(nThreads, nThreads,
                                      	0L, TimeUnit.MILLISECONDS,
                                      	new LinkedBlockingQueue());

Parameter corePoolSize (bilangan utas yang akan sedia (bermula) apabila perkhidmatan pelaksana bermula) dan maksimumPoolSize (bilangan maksimum utas yang boleh dibuat oleh perkhidmatan pelaksana ) menerima nilai yang sama — bilangan utas yang dihantar ke newFixedThreadPool(nThreads ) . Dan kita boleh lulus pelaksanaan ThreadFactory kita sendiri dengan cara yang sama.

Baiklah, mari kita lihat mengapa kita memerlukan ExecutorService sedemikian .

Berikut ialah logik ExecutorService dengan nombor tetap (n) benang:

  • Maksimum n utas akan aktif untuk memproses tugas.
  • Jika lebih daripada n tugasan diserahkan, tugasan itu akan disimpan dalam baris gilir sehingga utas menjadi bebas.
  • Jika salah satu benang gagal dan ditamatkan, benang baharu akan dibuat untuk menggantikannya.
  • Mana-mana benang dalam kolam aktif sehingga kolam ditutup.

Sebagai contoh, bayangkan menunggu untuk melalui keselamatan di lapangan terbang. Semua orang berdiri dalam satu barisan sehingga sejurus sebelum pemeriksaan keselamatan, penumpang diedarkan di antara semua pusat pemeriksaan yang bekerja. Jika terdapat kelewatan di salah satu pusat pemeriksaan, baris gilir hanya akan diproses oleh yang kedua sehingga yang pertama adalah percuma. Dan jika satu pusat pemeriksaan ditutup sepenuhnya, maka pusat pemeriksaan lain akan dibuka untuk menggantikannya, dan penumpang akan terus diproses melalui dua pusat pemeriksaan.

Kami akan ambil perhatian segera bahawa walaupun keadaan sesuai — benang n yang dijanjikan berfungsi dengan stabil, dan utas yang berakhir dengan ralat sentiasa diganti (sesuatu yang tidak mungkin dicapai oleh sumber terhad di lapangan terbang sebenar) — sistem masih mempunyai beberapa ciri yang tidak menyenangkan, kerana dalam apa jua keadaan tidak akan terdapat lebih banyak benang, walaupun barisan berkembang lebih cepat daripada benang boleh memproses tugas.

Saya cadangkan mendapatkan pemahaman praktikal tentang cara ExecutorService berfungsi dengan bilangan benang yang tetap. Mari buat kelas yang melaksanakan Runnable . Objek kelas ini mewakili tugas kami untuk ExecutorService .


public class Task implements Runnable {
    int taskNumber;
 
    public Task(int taskNumber) {
        this.taskNumber = taskNumber;
    }
 
    @Override
    public void run() {
try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Processed user request #" + taskNumber + " on thread " + Thread.currentThread().getName());
    }
}
    

Dalam kaedah run() , kami menyekat utas selama 2 saat, mensimulasikan beberapa beban kerja, dan kemudian memaparkan nombor tugas semasa dan nama utas yang melaksanakan tugas.


ExecutorService executorService = Executors.newFixedThreadPool(3);
 
        for (int i = 0; i < 30; i++) {
            executorService.execute(new Task(i));
        }
        
        executorService.shutdown();
    

Sebagai permulaan, dalam kaedah utama , kami mencipta ExecutorService dan menyerahkan 30 tugasan untuk dilaksanakan.

Permintaan pengguna diproses #1 pada utas pool-1-thread-2
Permintaan pengguna diproses #0 pada thread pool-1-thread-1
Permintaan pengguna diproses #2 pada thread pool-1-thread-3 Permintaan
pengguna diproses #5 pada pool- 1-thread-3 thread
Permintaan pengguna diproses #3 pada pool-1-thread-2 thread
Permintaan pengguna diproses #4 on pool-1-thread-1 thread
Permintaan pengguna diproses #8 pada pool-1-thread-1 thread
Pengguna diproses permintaan #6 pada thread pool-1-thread-3
Permintaan pengguna diproses #7 pada thread pool-1-thread-2
Permintaan pengguna diproses #10 pada thread pool-1-thread-3 Permintaan
pengguna diproses #9 pada pool-1- thread-1 thread
Permintaan pengguna diproses #11 pada pool-1-thread-2 thread
Permintaan pengguna diproses #12 on pool-1-thread-3 thread
Permintaan pengguna diproses #14 pada utas pool-1-thread-2
Permintaan pengguna diproses #13 pada thread pool-1-thread-1
Permintaan pengguna diproses #15 pada thread pool-1-thread-3
Permintaan pengguna diproses #16 pada pool- 1-thread-2 thread
Permintaan pengguna diproses #17 on pool-1-thread-1 thread
Permintaan pengguna diproses #18 on pool-1-thread-3 thread
Permintaan pengguna diproses #19 on pool-1-thread-2 thread
Pengguna diproses request #20 on pool-1-thread-1 thread
Permintaan pengguna diproses #21 on pool-1-thread-3 thread
Permintaan pengguna diproses #22 on pool-1-thread-2 thread
Permintaan pengguna diproses #23 on pool-1- thread-1 thread
Permintaan pengguna diproses #25 pada pool-1-thread-2 thread
Permintaan pengguna diproses #24 on pool-1-thread-3 thread
Permintaan pengguna diproses #26 pada utas pool-1-thread-1
Permintaan pengguna diproses #27 pada thread pool-1-thread-2
Permintaan pengguna diproses #28 pada thread pool-1-thread-3
Permintaan pengguna diproses #29 pada pool- 1-benang-1 utas

Output konsol menunjukkan kepada kita cara tugasan dilaksanakan pada utas yang berbeza sebaik sahaja ia dikeluarkan oleh tugas sebelumnya.

Kini kami akan menambah bilangan tugasan kepada 100, dan selepas menyerahkan 100 tugasan, kami akan memanggil kaedah awaitTermination(11, SECONDS) . Kami lulus nombor dan unit masa sebagai hujah. Kaedah ini akan menyekat benang utama selama 11 saat. Kemudian kami akan memanggil shutdownNow() untuk memaksa ExecutorService ditutup tanpa menunggu semua tugas selesai.


ExecutorService executorService = Executors.newFixedThreadPool(3);
 
        for (int i = 0; i < 100; i++) {
            executorService.execute(new Task(i));
        }
 
        executorService.awaitTermination(11, SECONDS);
 
        executorService.shutdownNow();
        System.out.println(executorService);
    

Pada akhirnya, kami akan memaparkan maklumat tentang keadaan executorService .

Inilah output konsol yang kami dapat:

Permintaan pengguna diproses #0 pada utas pool-1-thread-1
Permintaan pengguna diproses #2 pada thread pool-1-thread-3
Permintaan pengguna diproses #1 pada thread pool-1-thread-2
Permintaan pengguna diproses #4 pada pool- 1-thread-3 thread
Permintaan pengguna diproses #5 pada pool-1-thread-2 thread
Permintaan pengguna diproses #3 on pool-1-thread-1 thread
Permintaan pengguna diproses #6 on pool-1-thread-3 thread
Pengguna diproses permintaan #7 pada thread pool-1-thread-2
Permintaan pengguna diproses #8 pada thread pool-1-thread-1
Permintaan pengguna diproses #9 pada thread pool-1-thread-3 Permintaan
pengguna diproses #11 pada pool-1- thread-1 thread
Permintaan pengguna diproses #10 pada pool-1-thread-2 thread
Permintaan pengguna diproses #13 on pool-1-thread-1 thread
Permintaan pengguna diproses #14 pada utas pool-1-thread-2
Permintaan pengguna diproses #12 pada thread pool-1-thread-3
java.util.concurrent.ThreadPoolExecutor@452b3a41[Mematikan, saiz kolam = 3, utas aktif = 3 , tugasan beratur = 0, tugasan selesai = 15]
Permintaan pengguna diproses #17 pada utas pool-1-thread-3
Permintaan pengguna diproses #15 pada utas pool-1-thread-1 Permintaan
pengguna diproses #16 pada thread-1-thread -2 utas

Ini diikuti oleh 3 InterruptedExceptions , dilemparkan oleh kaedah tidur daripada 3 tugasan aktif.

Kita dapat melihat bahawa apabila program tamat, 15 tugasan selesai, tetapi kumpulan masih mempunyai 3 utas aktif yang tidak selesai melaksanakan tugas mereka. Kaedah interrupt() dipanggil pada tiga utas ini, yang bermaksud bahawa tugas akan selesai, tetapi dalam kes kami, kaedah tidur membuang InterruptedException . Kami juga melihat bahawa selepas kaedah shutdownNow() dipanggil, baris gilir tugas dikosongkan.

Oleh itu, apabila menggunakan ExecutorService dengan bilangan benang tetap dalam kumpulan, pastikan anda mengingati cara ia berfungsi. Jenis ini sesuai untuk tugasan dengan beban malar yang diketahui.

Berikut ialah satu lagi soalan yang menarik: jika anda perlu menggunakan pelaksana untuk satu utas, kaedah manakah yang harus anda panggil? newSingleThreadExecutor() atau newFixedThreadPool(1) ?

Kedua-dua pelaksana akan mempunyai tingkah laku yang setara. Satu-satunya perbezaan ialah kaedah newSingleThreadExecutor() akan mengembalikan pelaksana yang tidak boleh dikonfigurasikan semula kemudian untuk menggunakan utas tambahan.