Jenis kumpulan utas lainnya adalah "di-cache". Kumpulan utas seperti itu biasa digunakan sebagai kumpulan tetap.

Seperti yang ditunjukkan oleh namanya, kumpulan utas ini menyimpan utas. Itu membuat utas yang tidak terpakai tetap hidup untuk waktu yang terbatas untuk menggunakan kembali utas tersebut untuk melakukan tugas baru. Kumpulan utas seperti itu paling baik untuk saat kita memiliki pekerjaan ringan dalam jumlah yang wajar.

Arti dari "jumlah yang masuk akal" agak luas, tetapi Anda harus tahu bahwa kumpulan seperti itu tidak cocok untuk setiap jumlah tugas. Misalnya, kita ingin membuat sejuta tugas. Bahkan jika masing-masing membutuhkan waktu yang sangat singkat, kami masih akan menggunakan sumber daya yang tidak masuk akal dan menurunkan kinerja. Kita juga harus menghindari kumpulan seperti itu ketika waktu eksekusi tidak dapat diprediksi, misalnya, dengan tugas I/O.

Di bawah tenda, konstruktor ThreadPoolExecutor dipanggil dengan argumen berikut:

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
      new SynchronousQueue<Runnable>());
}

Nilai-nilai berikut diteruskan ke konstruktor sebagai argumen:

Parameter Nilai
corePoolSize (berapa banyak utas yang akan siap (dimulai) saat layanan pelaksana dimulai) 0
maximumPoolSize (jumlah maksimum utas yang dapat dibuat oleh layanan pelaksana ) Bilangan bulat.MAX_VALUE
keepAliveTime (waktu utas yang dibebaskan akan terus hidup sebelum dihancurkan jika jumlah utas lebih besar dari corePoolSize ) 60L
satuan (satuan waktu) Satuan Waktu.SECONDS
workQueue (implementasi antrian) SynchronousQueue baru<Runnable>()

Dan kita bisa melewati implementasi ThreadFactory kita sendiri dengan cara yang persis sama.

Mari kita bicara tentang SynchronousQueue

Ide dasar transfer sinkron cukup sederhana namun kontra-intuitif (yaitu, intuisi atau akal sehat memberi tahu Anda bahwa itu salah): Anda dapat menambahkan elemen ke antrean jika dan hanya jika utas lain menerima elemen di waktu yang sama. Dengan kata lain, antrean sinkron tidak dapat memiliki tugas di dalamnya, karena segera setelah tugas baru tiba, utas pelaksana telah mengambil tugas tersebut .

Saat tugas baru memasuki antrean, jika ada utas aktif gratis di kumpulan, tugas tersebut akan diambil. Jika semua utas sibuk, maka utas baru dibuat.

Kumpulan cache dimulai dengan nol utas dan berpotensi berkembang menjadi utas Integer.MAX_VALUE . Pada dasarnya, ukuran kumpulan thread yang di-cache hanya dibatasi oleh sumber daya sistem.

Untuk menghemat sumber daya sistem, kumpulan utas yang di-cache menghapus utas yang menganggur selama satu menit.

Mari kita lihat cara kerjanya dalam praktik. Kami akan membuat kelas tugas yang memodelkan permintaan pengguna:

public class Task implements Runnable {
   int taskNumber;

   public Task(int taskNumber) {
       this.taskNumber = taskNumber;
   }

   @Override
   public void run() {
       System.out.println("Processed user request #" + taskNumber + " on thread " + Thread.currentThread().getName());
   }
}

Dalam metode utama , kami membuat newCachedThreadPool dan kemudian menambahkan 3 tugas untuk dieksekusi. Di sini kami mencetak status layanan kami (1) .

Selanjutnya, kita jeda selama 30 detik, memulai tugas lain, dan menampilkan status (2) .

Setelah itu, kami menjeda utas utama kami selama 70 detik, mencetak status (3) , lalu menambahkan 3 tugas lagi, dan mencetak lagi status (4) .

Di tempat kami menampilkan status segera setelah menambahkan tugas, pertama-tama kami menambahkan tidur 1 detik untuk keluaran terbaru.

ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 3; i++) {
            executorService.submit(new Task(i));
        }

        TimeUnit.SECONDS.sleep(1);
            System.out.println(executorService);	//(1)

        TimeUnit.SECONDS.sleep(30);

        executorService.submit(new Task(3));
        TimeUnit.SECONDS.sleep(1);
            System.out.println(executorService);	//(2)

        TimeUnit.SECONDS.sleep(70);

            System.out.println(executorService);	//(3)

        for (int i = 4; i < 7; i++) {
            executorService.submit(new Task(i));
        }

        TimeUnit.SECONDS.sleep(1);
            System.out.println(executorService);	//(4)
        executorService.shutdown();

Dan inilah hasilnya:

Permintaan pengguna yang diproses #0 pada utas kumpulan-1-utas-1
Permintaan pengguna yang diproses #1 pada utas kumpulan-1-utas-2
Permintaan pengguna yang diproses #2 pada utas kumpulan-1-utas-3
(1) java.util.concurrent .ThreadPoolExecutor@f6f4d33[Berjalan, ukuran kumpulan = 3, utas aktif = 0, tugas antri = 0, tugas selesai = 3]
Memproses permintaan pengguna #3 pada kumpulan-1-utas-2 utas
(2) java.util.concurrent. ThreadPoolExecutor@f6f4d33[Berjalan, ukuran kumpulan = 3, utas aktif = 0, tugas antri = 0, tugas selesai = 4]
(3) java.util.concurrent.ThreadPoolExecutor@f6f4d33[Berjalan, ukuran kumpulan = 0, utas aktif = 0 , tugas antri = 0, tugas selesai = 4]
Permintaan pengguna yang diproses #4 pada utas kumpulan-1-utas-4
Permintaan pengguna yang diproses #5 pada utas kumpulan-1-utas-5
Permintaan pengguna yang diproses #6 pada pool-1-thread-4 thread
(4) java.util.concurrent.ThreadPoolExecutor@f6f4d33[Berjalan, ukuran kumpulan = 2, thread aktif = 0, tugas antri = 0, tugas selesai = 7]

Mari kita bahas setiap langkahnya:

Melangkah Penjelasan
1 (setelah 3 tugas selesai) Kami membuat 3 utas, dan 3 tugas dijalankan pada ketiga utas ini.
Saat status ditampilkan, ketiga tugas selesai, dan utas siap untuk melakukan tugas lainnya.
2 (setelah jeda 30 detik dan pelaksanaan tugas lain) Setelah 30 detik tidak aktif, utas masih aktif dan menunggu tugas.
Tugas lain ditambahkan dan dijalankan pada utas yang diambil dari kumpulan utas langsung yang tersisa.
Tidak ada utas baru yang ditambahkan ke kumpulan.
3 (setelah jeda 70 detik) Utas telah dihapus dari kolam.
Tidak ada utas yang siap menerima tugas.
4 (setelah menjalankan 3 tugas lagi) Setelah lebih banyak tugas diterima, utas baru dibuat. Kali ini hanya dua utas yang berhasil memproses 3 tugas.

Nah, sekarang Anda sudah mengenal logika jenis layanan eksekutor lainnya.

Dengan analogi dengan metode lain dari kelas utilitas Pelaksana , metode newCachedThreadPool juga memiliki versi kelebihan muatan yang menggunakan objek ThreadFactory sebagai argumen.