Metode newFixedThreadPool dari kelas Pelaksana membuat ExecutorService dengan jumlah utas tetap. Berbeda dengan metode newSingleThreadExecutor , kami menentukan berapa banyak utas yang kami inginkan di kumpulan. Di bawah tenda, kode berikut disebut:


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

Parameter corePoolSize (jumlah utas yang akan siap (dimulai) saat layanan pelaksana dimulai) dan maximumPoolSize (jumlah maksimum utas yang dapat dibuat oleh layanan pelaksana ) menerima nilai yang sama — jumlah utas yang diteruskan ke newFixedThreadPool(nThreads ) . Dan kita bisa melewati implementasi ThreadFactory kita sendiri dengan cara yang persis sama.

Baiklah, mari kita lihat mengapa kita membutuhkan ExecutorService seperti itu .

Inilah logika ExecutorService dengan jumlah (n) utas yang tetap:

  • Maksimal n utas akan aktif untuk memproses tugas.
  • Jika lebih dari n tugas dikirimkan, tugas tersebut akan ditahan dalam antrean hingga utas menjadi bebas.
  • Jika salah satu utas gagal dan berakhir, utas baru akan dibuat untuk menggantikannya.
  • Utas apa pun di kumpulan aktif hingga kumpulan ditutup.

Sebagai contoh, bayangkan menunggu untuk melewati keamanan di bandara. Semua orang berdiri dalam satu baris sampai sesaat sebelum pemeriksaan keamanan, penumpang didistribusikan ke semua pos pemeriksaan yang berfungsi. Jika ada penundaan di salah satu pos pemeriksaan, antrian hanya akan diproses oleh pos kedua hingga pos pertama bebas. Dan jika satu pos pemeriksaan ditutup seluruhnya, maka pos pemeriksaan lain akan dibuka untuk menggantikannya, dan penumpang akan terus diproses melalui dua pos pemeriksaan.

Kami akan segera mencatat bahwa meskipun kondisinya ideal — n utas yang dijanjikan bekerja dengan stabil, dan utas yang diakhiri dengan kesalahan selalu diganti (sesuatu yang tidak mungkin dicapai oleh sumber daya yang terbatas di bandara nyata) — sistem masih memiliki beberapa fitur yang tidak menyenangkan, karena dalam situasi apa pun tidak akan ada lebih banyak utas, bahkan jika antrean bertambah lebih cepat daripada utas yang dapat memproses tugas.

Saya menyarankan untuk mendapatkan pemahaman praktis tentang bagaimana ExecutorService bekerja dengan jumlah utas yang tetap. Mari buat kelas yang mengimplementasikan Runnable . Objek kelas ini mewakili tugas kita 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 metode run() , kami memblokir utas selama 2 detik, mensimulasikan beberapa beban kerja, lalu menampilkan nomor tugas saat ini dan nama utas yang menjalankan tugas.


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

Untuk memulainya, dalam metode utama , kami membuat ExecutorService dan mengirimkan 30 tugas untuk dieksekusi.

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

Output konsol menunjukkan kepada kita bagaimana tugas dijalankan pada utas yang berbeda setelah dirilis oleh tugas sebelumnya.

Sekarang kita akan menambah jumlah tugas menjadi 100, dan setelah mengirimkan 100 tugas, kita akan memanggil metode awaitTermination(11, SECONDS) . Kami memberikan angka dan satuan waktu sebagai argumen. Metode ini akan memblokir utas utama selama 11 detik. Kemudian kita akan memanggil shutdownNow() untuk memaksa ExecutorService dimatikan 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 menampilkan informasi tentang status executorService .

Inilah keluaran konsol yang kami dapatkan:

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

Ini diikuti oleh 3 InterruptedExceptions , dilemparkan oleh metode tidur dari 3 tugas aktif.

Kita dapat melihat bahwa ketika program berakhir, 15 tugas selesai, tetapi kumpulan masih memiliki 3 utas aktif yang tidak menyelesaikan tugasnya. Metode interrupt() dipanggil pada ketiga utas ini, yang berarti bahwa tugas akan selesai, tetapi dalam kasus kita, metode tidur melontarkan InterruptedException . Kami juga melihat bahwa setelah metode shutdownNow() dipanggil, antrian tugas dihapus.

Jadi saat menggunakan ExecutorService dengan jumlah utas tetap di kumpulan, pastikan untuk mengingat cara kerjanya. Jenis ini cocok untuk tugas dengan beban konstan yang diketahui.

Inilah pertanyaan menarik lainnya: jika Anda perlu menggunakan eksekutor untuk satu utas, metode mana yang harus Anda panggil? newSingleThreadExecutor() atau newFixedThreadPool(1) ?

Kedua pelaksana akan memiliki perilaku yang setara. Satu-satunya perbedaan adalah bahwa metode newSingleThreadExecutor() akan mengembalikan eksekutor yang nantinya tidak dapat dikonfigurasi ulang untuk menggunakan utas tambahan.