newCachedThreadPool()
melawan newFixedThreadPool()
Kapan saya harus menggunakan satu atau yang lain? Strategi mana yang lebih baik dalam hal pemanfaatan sumber daya?
newCachedThreadPool()
melawan newFixedThreadPool()
Kapan saya harus menggunakan satu atau yang lain? Strategi mana yang lebih baik dalam hal pemanfaatan sumber daya?
Saya pikir dokumen menjelaskan perbedaan dan penggunaan kedua fungsi ini dengan cukup baik:
Membuat kumpulan utas yang menggunakan kembali sejumlah utas yang beroperasi dari antrian yang tidak dibatasi bersama. Pada titik mana pun, paling banyak nThreads threads akan menjadi tugas pemrosesan aktif. Jika tugas tambahan diajukan ketika semua utas aktif, mereka akan menunggu dalam antrian sampai utas tersedia. Jika ada utas yang berakhir karena kegagalan selama eksekusi sebelum shutdown, yang baru akan menggantikannya jika diperlukan untuk menjalankan tugas selanjutnya. Utas di kumpulan akan ada sampai ditutup secara eksplisit.
Membuat kumpulan utas yang membuat utas baru sesuai kebutuhan, tetapi akan menggunakan ulang utas yang sebelumnya dibuat saat tersedia. Kumpulan ini biasanya akan meningkatkan kinerja program yang menjalankan banyak tugas asinkron yang berumur pendek. Panggilan untuk mengeksekusi akan menggunakan kembali utas yang dibangun sebelumnya jika tersedia. Jika tidak ada utas yang tersedia, utas baru akan dibuat dan ditambahkan ke kumpulan. Utas yang belum digunakan selama enam puluh detik diakhiri dan dihapus dari cache. Dengan demikian, kolam yang tetap menganggur cukup lama tidak akan mengkonsumsi sumber daya apa pun. Perhatikan bahwa kumpulan dengan properti yang serupa tetapi detail yang berbeda (misalnya, parameter batas waktu) dapat dibuat menggunakan konstruktor ThreadPoolExecutor.
Dalam hal sumber daya, newFixedThreadPool
akan menjaga semua utas berjalan sampai mereka secara eksplisit dihentikan. Di newCachedThreadPool
Thread yang belum digunakan selama enam puluh detik diakhiri dan dihapus dari cache.
Mengingat hal ini, konsumsi sumber daya akan sangat tergantung pada situasi. Sebagai contoh, Jika Anda memiliki banyak tugas yang berjalan lama saya sarankan FixedThreadPool
. Adapun CachedThreadPool
, dokumen mengatakan bahwa "Kumpulan ini biasanya akan meningkatkan kinerja program yang menjalankan banyak tugas asinkron yang berumur pendek".
newCachedThreadPool
mungkin menyebabkan beberapa masalah serius karena Anda meninggalkan semua kontrol kethread pool
dan ketika layanan bekerja dengan orang lain di host yang sama , yang mungkin menyebabkan yang lain macet karena menunggu CPU lama. Jadi saya pikirnewFixedThreadPool
bisa lebih aman dalam skenario semacam ini. Juga posting ini menjelaskan perbedaan paling menonjol di antara mereka.Hanya untuk melengkapi jawaban lain, saya ingin mengutip Effective Java, 2nd Edition, oleh Joshua Bloch, bab 10, Butir 68:
sumber
Jika Anda melihat kode sumber , Anda akan melihat, mereka memanggil ThreadPoolExecutor. internal dan mengatur propertinya. Anda dapat membuatnya untuk memiliki kontrol yang lebih baik terhadap kebutuhan Anda.
sumber
Jika Anda tidak khawatir tentang antrean tugas Callable / Runnable yang tidak terbatas , Anda dapat menggunakan salah satunya. Seperti yang disarankan oleh bruno, aku juga lebih memilih
newFixedThreadPool
untuknewCachedThreadPool
lebih dari dua ini.Tetapi ThreadPoolExecutor menyediakan fitur yang lebih fleksibel dibandingkan dengan salah satu
newFixedThreadPool
ataunewCachedThreadPool
Keuntungan:
Anda memiliki kontrol penuh ukuran BlockingQueue . Ini bukan tanpa batas, tidak seperti dua opsi sebelumnya. Saya tidak akan mendapatkan kesalahan memori karena banyak tugas Callable / Runnable yang tertunda saat ada turbulensi yang tidak terduga dalam sistem.
Anda dapat menerapkan kebijakan penanganan Penolakan khusus ATAU menggunakan salah satu kebijakan:
Secara default
ThreadPoolExecutor.AbortPolicy
, pawang melempar runtime RejectedExecutionException saat penolakan.Di
ThreadPoolExecutor.CallerRunsPolicy
, utas yang menjalankan eksekusi itu sendiri menjalankan tugas. Ini memberikan mekanisme kontrol umpan balik sederhana yang akan memperlambat laju tugas baru yang diajukan.Dalam
ThreadPoolExecutor.DiscardPolicy
, tugas yang tidak dapat dieksekusi dijatuhkan begitu saja.Di
ThreadPoolExecutor.DiscardOldestPolicy
, jika pelaksana tidak dimatikan, tugas di kepala antrian pekerjaan dijatuhkan, dan kemudian eksekusi dicoba (yang bisa gagal lagi, menyebabkan ini terulang.)Anda dapat menerapkan pabrik Thread khusus untuk kasus penggunaan di bawah ini:
sumber
Itu benar,
Executors.newCachedThreadPool()
bukan pilihan tepat untuk kode server yang melayani banyak klien dan permintaan bersamaan.Mengapa? Pada dasarnya ada dua (terkait) masalah dengan itu:
Tidak terikat, yang berarti Anda membuka pintu bagi siapa saja untuk melumpuhkan JVM Anda dengan hanya menyuntikkan lebih banyak pekerjaan ke dalam layanan (serangan DoS). Utas mengkonsumsi jumlah memori yang tidak dapat diabaikan dan juga meningkatkan konsumsi memori berdasarkan pekerjaan mereka yang sedang berjalan, jadi cukup mudah untuk menjatuhkan server dengan cara ini (kecuali jika Anda memiliki pemutus arus lain di tempat).
Masalah yang tidak terbatas diperburuk oleh fakta bahwa Pelaksana digawangi oleh
SynchronousQueue
yang berarti ada handoff langsung antara pemberi tugas dan kumpulan utas. Setiap tugas baru akan membuat utas baru jika semua utas yang ada sibuk. Ini umumnya strategi yang buruk untuk kode server. Saat CPU jenuh, tugas yang ada membutuhkan waktu lebih lama untuk diselesaikan. Namun, lebih banyak tugas sedang dikirim dan lebih banyak utas dibuat, sehingga tugas membutuhkan waktu lebih lama dan lebih lama untuk diselesaikan. Ketika CPU jenuh, lebih banyak utas jelas bukan yang dibutuhkan server.Berikut rekomendasi saya:
Gunakan kolam ulir ukuran tetap Pelaksana.newFixedThreadPool atau ThreadPoolExecutor. dengan jumlah utas maksimum yang ditetapkan;
sumber
The
ThreadPoolExecutor
kelas adalah implementasi dasar untuk pelaksana yang kembali dari banyakExecutors
metode pabrik. Jadi mari kita mendekati kumpulan thread Tetap dan Cached dariThreadPoolExecutor
perspektif.ThreadPoolExecutor
The konstruktor utama dari kelas ini terlihat seperti ini:
Ukuran Kolam Inti
The
corePoolSize
menentukan ukuran minimum dari kolam renang sasaran benang.Implementasi akan mempertahankan kumpulan ukuran itu bahkan jika tidak ada tugas untuk dieksekusi.Ukuran Kolam Maksimum
Ini
maximumPoolSize
adalah jumlah maksimum utas yang dapat aktif sekaligus.Setelah kumpulan thread tumbuh dan menjadi lebih besar dari
corePoolSize
ambang, pelaksana dapat menghentikan utas menganggur dan mencapai kecorePoolSize
lagi. JikaallowCoreThreadTimeOut
benar, maka pelaksana bahkan dapat menghentikan utas inti jika mereka menganggur lebih darikeepAliveTime
ambang.Jadi intinya adalah jika utas tetap menganggur lebih dari
keepAliveTime
ambang, mereka dapat diakhiri karena tidak ada permintaan untuk mereka.Antrian
Apa yang terjadi ketika tugas baru masuk dan semua utas inti terisi? Tugas-tugas baru akan antri di dalamnya
BlockingQueue<Runnable>
instance itu. Ketika utas menjadi gratis, salah satu tugas yang antri dapat diproses.Ada implementasi yang berbeda dari
BlockingQueue
antarmuka di Jawa, sehingga kami dapat menerapkan pendekatan antrian yang berbeda seperti:Bounded Queue : Tugas baru akan diantrikan di dalam antrian tugas yang dibatasi.
Antrian Tanpa Batas : Tugas baru akan di-antri di dalam antrian tugas tidak terikat. Jadi antrian ini dapat tumbuh sebanyak ukuran tumpukan memungkinkan.
Synchronous Handoff : Kita juga dapat menggunakan
SynchronousQueue
untuk mengantri tugas-tugas baru. Dalam hal itu, ketika mengantri tugas baru, utas lain harus sudah menunggu tugas itu.Pengajuan Pekerjaan
Begini cara
ThreadPoolExecutor
menjalankan tugas baru:corePoolSize
utas berjalan, cobalah untuk memulai utas baru dengan tugas yang diberikan sebagai pekerjaan pertama.BlockingQueue#offer
metode ini. Theoffer
Metode tidak akan memblokir jika antrian penuh dan segera kembalifalse
.offer
kembalifalse
), maka ia mencoba untuk menambahkan utas baru ke kumpulan utas dengan tugas ini sebagai pekerjaan pertama.RejectedExecutionHandler
.Perbedaan utama antara kolam ulir tetap dan cache bermuara pada tiga faktor ini:
Fixed Thread Pool
Begini cara
Excutors.newFixedThreadPool(n)
kerjanya:Seperti yang Anda lihat:
OutOfMemoryError
.Kumpulan thread berukuran tetap tampaknya menjadi kandidat yang baik ketika kita akan membatasi jumlah tugas bersamaan untuk tujuan manajemen sumber daya .
Misalnya, jika kita akan menggunakan pelaksana untuk menangani permintaan server web, pelaksana tetap dapat menangani permintaan yang meledak secara lebih masuk akal.
Untuk manajemen sumber daya yang lebih baik lagi, sangat disarankan untuk membuat kebiasaan
ThreadPoolExecutor
denganBlockingQueue<T>
implementasi terbatas disertai dengan wajarRejectedExecutionHandler
.Cached Thread Pool
Begini cara
Executors.newCachedThreadPool()
kerjanya:Seperti yang Anda lihat:
Integer.MAX_VALUE
. Secara praktis, kumpulan utas tidak terbatas.SynchronousQueue
selalu gagal ketika tidak ada orang di ujung sana yang menerimanya!Gunakan saat Anda memiliki banyak tugas jangka pendek yang dapat diprediksi.
sumber
Anda harus menggunakan newCachedThreadPool hanya ketika Anda memiliki tugas asinkron yang berumur pendek seperti yang dinyatakan dalam Javadoc, jika Anda mengirimkan tugas yang membutuhkan waktu lebih lama untuk diproses, Anda pada akhirnya akan membuat terlalu banyak utas. Anda dapat menekan CPU 100% jika Anda mengirimkan tugas yang sedang berjalan dengan kecepatan lebih tinggi ke newCachedThreadPool ( http://rashcoder.com/be-careful-while-using-executors-newcachedthreadpool/ ).
sumber
Saya melakukan beberapa tes cepat dan memiliki temuan berikut:
1) jika menggunakan SynchronousQueue:
Setelah utas mencapai ukuran maksimum, setiap karya baru akan ditolak dengan pengecualian seperti di bawah ini.
2) jika menggunakan LinkedBlockingQueue:
Thread tidak pernah bertambah dari ukuran minimum ke ukuran maksimum, artinya kumpulan thread adalah ukuran tetap sebagai ukuran minimum.
sumber