FixedThreadPool vs CachedThreadPool: lebih kecil dari dua kejahatan

94

Saya memiliki program yang memunculkan utas (~ 5-150) yang melakukan banyak tugas. Awalnya, saya menggunakan FixedThreadPoolkarena pertanyaan serupa ini menyarankan mereka lebih cocok untuk tugas yang berumur lebih lama dan dengan pengetahuan saya yang sangat terbatas tentang multithreading, saya menganggap rata-rata umur utas (beberapa menit) " berumur panjang ".

Namun, saya baru-baru ini menambahkan kemampuan untuk menelurkan utas tambahan dan melakukannya membawa saya di atas batas utas yang saya tetapkan. Dalam hal ini, apakah lebih baik menebak dan menambah jumlah utas yang dapat saya izinkan atau beralih ke CachedThreadPooljadi saya tidak memiliki utas yang terbuang?

Mencoba mereka berdua keluar preliminarily, ada tidak tampaknya menjadi perbedaan jadi aku cenderung untuk pergi dengan CachedThreadPoolhanya untuk menghindari sampah. Namun, apakah rentang hidup utas berarti saya harus memilih FixedThreadPooldan hanya menangani utas yang tidak digunakan? Pertanyaan ini membuatnya tampak seperti utas ekstra itu tidak sia-sia, tetapi saya akan menghargai klarifikasi.

Daniel
sumber

Jawaban:

108

CachedThreadPool adalah persis apa yang harus Anda gunakan untuk situasi Anda karena tidak ada konsekuensi negatif untuk menggunakannya untuk utas yang berjalan lama. Komentar di dokumen java tentang CachedThreadPools yang cocok untuk tugas-tugas pendek hanya menunjukkan bahwa mereka sangat sesuai untuk kasus-kasus seperti itu, bukan karena mereka tidak dapat atau tidak boleh digunakan untuk tugas-tugas yang melibatkan tugas-tugas yang berjalan lama.

Untuk menjelaskan lebih lanjut, Executors.newCachedThreadPool dan Executors.newFixedThreadPool keduanya didukung oleh implementasi kumpulan utas yang sama (setidaknya di JDK terbuka) hanya dengan parameter yang berbeda. Perbedaannya hanya pada jumlah minimum, maksimum, waktu penghentian thread, dan jenis antriannya.

public static ExecutorService newFixedThreadPool(int nThreads) {
     return new ThreadPoolExecutor(nThreads, nThreads,
                                   0L, TimeUnit.MILLISECONDS,
                                   new LinkedBlockingQueue<Runnable>());
 }

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

FixedThreadPool memang memiliki kelebihan jika Anda sebenarnya ingin bekerja dengan jumlah utas yang tetap, karena itu Anda dapat mengirimkan sejumlah tugas ke layanan pelaksana sambil mengetahui bahwa jumlah utas akan dipertahankan pada tingkat yang Anda tentukan. Jika Anda secara eksplisit ingin menambah jumlah utas, maka ini bukan pilihan yang tepat.

Namun ini tidak berarti bahwa satu masalah yang mungkin Anda miliki dengan CachedThreadPool adalah berkaitan dengan membatasi jumlah utas yang berjalan secara bersamaan. CachedThreadPool tidak akan membatasinya untuk Anda, jadi Anda mungkin perlu menulis kode Anda sendiri untuk memastikan bahwa Anda tidak menjalankan terlalu banyak utas. Ini sangat tergantung pada desain aplikasi Anda dan bagaimana tugas dikirimkan ke layanan pelaksana.

Trevor Freeman
sumber
1
"CachedThreadPool adalah persis apa yang harus Anda gunakan untuk situasi Anda karena tidak ada konsekuensi negatif menggunakan satu untuk utas yang berjalan lama". Saya tidak berpikir saya setuju. CachedThreadPool secara dinamis membuat utas tanpa batas atas. Tugas yang berjalan lama pada jumlah thread yang besar berpotensi menghabiskan semua resource. Selain itu, memiliki lebih banyak utas daripada yang ideal dapat mengakibatkan terlalu banyak sumber daya yang terbuang percuma pada peralihan konteks utas ini. Meskipun Anda menjelaskan di akhir jawaban bahwa pelambatan khusus diperlukan, awal jawaban agak menyesatkan.
Nishit
1
Mengapa tidak membuat ThreadPoolExecutorlike yang dibatasi ThreadPoolExecutor(0, maximumPoolSize, 60L, TimeUnit.SECONDS, SynchronousQueue())?
Abhijit Sarkar
45

Kedua FixedThreadPooldan CachedThreadPoolyang jahat dalam aplikasi yang sangat dimuat.

CachedThreadPool lebih berbahaya dari FixedThreadPool

Jika aplikasi Anda sangat dimuat & menuntut latensi rendah, lebih baik hilangkan kedua opsi karena kekurangan di bawah ini

  1. Sifat antrian tugas yang tidak terbatas: Ini dapat menyebabkan kehabisan memori atau latensi tinggi
  2. Thread yang berjalan lama akan menyebabkan CachedThreadPoollepas kendali pada pembuatan Thread

Karena Anda tahu bahwa keduanya adalah kejahatan, kejahatan yang lebih kecil tidak ada gunanya. Lebih suka ThreadPoolExecutor , yang menyediakan kontrol terperinci pada banyak parameter.

  1. Tetapkan antrian tugas sebagai antrian terbatas untuk memiliki kontrol yang lebih baik
  2. Memiliki RejectionHandler yang tepat - RejectionHandler Anda sendiri atau penangan Default yang disediakan oleh JDK
  3. Jika Anda memiliki sesuatu untuk dilakukan sebelum / setelah menyelesaikan tugas, timpa beforeExecute(Thread, Runnable)danafterExecute(Runnable, Throwable)
  4. Ganti ThreadFactory jika kustomisasi utas diperlukan
  5. Kontrol ukuran kumpulan Benang secara dinamis pada waktu proses (pertanyaan SE terkait: Kumpulan Benang Dinamis )
Ravindra babu
sumber
Bagaimana jika seseorang memutuskan untuk menggunakan commonPool?
Crosk Cool
1
@Ravindra - Anda telah menjelaskan dengan indah kekurangan dari CachedThreadPool dan FixedThreadPool. Ini menunjukkan bahwa Anda telah mendapatkan pemahaman yang mendalam tentang paket konkurensi.
Ayaskant
5

Jadi saya memiliki program yang memunculkan utas (~ 5-150) yang melakukan banyak tugas.

Apakah Anda yakin Anda memahami bagaimana utas sebenarnya diproses oleh OS dan perangkat keras pilihan Anda? Bagaimana Java memetakan utas ke utas OS, bagaimana memetakan utas ke utas CPU, dll.? Saya bertanya karena membuat 150 utas di dalam SATU JRE hanya masuk akal jika Anda memiliki inti / utas CPU yang besar di bawahnya, yang kemungkinan besar tidak demikian. Bergantung pada OS dan RAM yang digunakan, membuat lebih dari n utas bahkan dapat mengakibatkan JRE Anda dihentikan karena kesalahan OOM. Jadi, Anda harus benar-benar membedakan antara utas dan pekerjaan yang harus dilakukan oleh utas itu, berapa banyak pekerjaan yang bahkan dapat Anda proses, dll.

Dan itulah masalah dengan CachedThreadPool: Tidak masuk akal untuk mengantri pekerjaan yang berjalan lama di utas yang sebenarnya tidak dapat berjalan karena Anda hanya memiliki 2 inti CPU yang dapat memproses utas tersebut. Jika Anda berakhir dengan 150 utas terjadwal, Anda mungkin membuat banyak overhead yang tidak perlu untuk penjadwal yang digunakan dalam Java dan OS untuk memprosesnya secara bersamaan. Ini tidak mungkin dilakukan jika Anda hanya memiliki 2 inti CPU, kecuali utas Anda menunggu I / O atau semacamnya sepanjang waktu. Tetapi bahkan dalam kasus itu banyak utas akan membuat banyak I / O ...

Dan masalah itu tidak terjadi dengan FixedThreadPool, yang dibuat dengan misalnya 2 + n utas, di mana n tentu saja cukup rendah, karena dengan perangkat keras dan sumber daya OS digunakan dengan overhead yang jauh lebih sedikit untuk mengelola utas yang tetap tidak dapat berjalan.

Thorsten Schöning
sumber
Terkadang tidak ada pilihan yang lebih baik, Anda hanya dapat memiliki 1 inti CPU tetapi jika Anda menjalankan server di mana setiap permintaan pengguna akan memicu utas untuk memproses permintaan, tidak akan ada pilihan lain yang masuk akal, terutama jika Anda berencana untuk menskalakan server setelah Anda mengembangkan basis pengguna.
Michel Feinstein
@mFeinstein Bagaimana bisa seseorang tidak memiliki pilihan jika seseorang berada dalam posisi untuk memilih implementasi kumpulan benang? Dalam contoh Anda dengan 1 inti CPU hanya menghasilkan lebih banyak utas sama sekali tidak masuk akal, itu sangat cocok dengan contoh saya menggunakan FixedThreadPool. Itu juga mudah diskalakan, pertama dengan satu atau dua utas pekerja, kemudian dengan 10 atau 15 tergantung pada jumlah inti.
Thorsten Schöning
2
Sebagian besar implementasi server web akan membuat satu utas baru untuk setiap permintaan HTTP baru ... Mereka tidak akan peduli tentang berapa banyak inti sebenarnya yang dimiliki mesin, ini membuat penerapan lebih sederhana dan lebih mudah untuk diskalakan. Ini berlaku untuk banyak desain lain di mana Anda hanya ingin membuat kode satu kali dan menerapkan, dan tidak perlu mengkompilasi ulang dan menerapkan ulang jika Anda mengubah mesin, yang bisa menjadi instance cloud.
Michel Feinstein
@mFeinstein Sebagian besar server web menggunakan kumpulan utas untuk permintaan mereka sendiri, hanya karena utas pemijahan yang tidak dapat berjalan tidak masuk akal, atau mereka menggunakan loop acara untuk koneksi dan memproses permintaan di kumpulan setelahnya atau semacamnya. Selain itu, Anda kehilangan intinya, yaitu bahwa pertanyaannya adalah tentang seseorang yang dapat memilih kumpulan utas yang benar dan utas pemijahan yang tidak dapat berjalan tetap tidak masuk akal. FixedthreadPool dikonfigurasi ke jumlah utas yang wajar per mesin tergantung pada skala inti dengan baik.
Thorsten Schöning
3
@ ThorstenSchöning, memiliki 50 utas yang terikat CPU pada mesin 2-inti tidak membantu. Memiliki 50 utas terikat IO pada mesin 2 inti bisa sangat membantu.
Paul Draper