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?
Jawaban:
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 ke thread pool
dan ketika layanan bekerja dengan orang lain di host yang sama , yang mungkin menyebabkan yang lain macet karena menunggu CPU lama. Jadi saya pikir newFixedThreadPool
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:
"Memilih layanan pelaksana untuk aplikasi tertentu bisa rumit. Jika Anda menulis program kecil , atau server yang sedikit dimuat , menggunakan Executors.new- CachedThreadPool umumnya merupakan pilihan yang baik , karena tidak memerlukan konfigurasi dan umumnya" melakukan hal yang benar." Tetapi kumpulan thread dalam cache bukan pilihan yang baik untuk server produksi yang sarat muatan !
Di kumpulan utas yang di-cache , tugas yang dikirimkan tidak diantrikan tetapi segera diserahkan ke utas untuk dieksekusi. Jika tidak ada utas tersedia, yang baru dibuat . Jika server dimuat sangat banyak sehingga semua CPU-nya sepenuhnya digunakan, dan lebih banyak tugas tiba, lebih banyak utas akan dibuat, yang hanya akan memperburuk masalah.
Oleh karena itu, di server produksi yang sarat muatan , Anda jauh lebih baik menggunakan Executors.newFixedThreadPool , yang memberi Anda kumpulan dengan jumlah thread yang tetap, atau menggunakan kelas ThreadPoolExecutor secara langsung, untuk kontrol maksimum. "
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.
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>());
}
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
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
RejectedExecutionHandler handler)
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:
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;
The ThreadPoolExecutor
kelas adalah implementasi dasar untuk pelaksana yang kembali dari banyak Executors
metode pabrik. Jadi mari kita mendekati kumpulan thread Tetap dan Cached dariThreadPoolExecutor
perspektif.
The konstruktor utama dari kelas ini terlihat seperti ini:
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler
)
The corePoolSize
menentukan ukuran minimum dari kolam renang sasaran benang.Implementasi akan mempertahankan kumpulan ukuran itu bahkan jika tidak ada tugas untuk dieksekusi.
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 ke corePoolSize
lagi. Jika allowCoreThreadTimeOut
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.
Apa yang terjadi ketika tugas baru masuk dan semua utas inti terisi? Tugas-tugas baru akan antri di dalamnyaBlockingQueue<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.
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. The offer
Metode tidak akan memblokir jika antrian penuh dan segera kembali false
.offer
kembali false
), 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:
+ ----------- + ----------- + ------------------- + ----- ---------------------------- + | Jenis Kolam | Ukuran Inti | Ukuran Maksimum | Strategi Antrian | + ----------- + ----------- + ------------------- + ----- ---------------------------- + | Diperbaiki | n (tetap) | n (tetap) | `LinkedBlockingQueue` | Tidak Terbatas + ----------- + ----------- + ------------------- + ----- ---------------------------- + | Dalam cache | 0 | Integer.MAX_VALUE | `SynchronousQueue` | + ----------- + ----------- + ------------------- + ----- ---------------------------- +
Excutors.newFixedThreadPool(n)
kerjanya:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
Seperti yang Anda lihat:
OutOfMemoryError
.Kapan saya harus menggunakan satu atau yang lain? Strategi mana yang lebih baik dalam hal pemanfaatan sumber daya?
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
dengan BlockingQueue<T>
implementasi terbatas disertai dengan wajar RejectedExecutionHandler
.
Begini cara Executors.newCachedThreadPool()
kerjanya:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
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!Kapan saya harus menggunakan satu atau yang lain? Strategi mana yang lebih baik dalam hal pemanfaatan sumber daya?
Gunakan saat Anda memiliki banyak tugas jangka pendek yang dapat diprediksi.
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/ ).
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.
Pengecualian di utas "utama" java.util.concurrent.RejectedExecutionException: Tugas java.util.concurrent.FutureTask@3fee733d ditolak dari java.util.concurrent.ThreadPoolExecutor@5acf9800 [Berjalan, ukuran kolam = 3, thread aktif = 3, tugas yang di-antrian = 0, tugas selesai = 0]
di java.util.concurrent.ThreadPoolExecutor $ AbortPolicy.rejectedExecution (ThreadPoolExecutor.java:2047)
2) jika menggunakan LinkedBlockingQueue:
Thread tidak pernah bertambah dari ukuran minimum ke ukuran maksimum, artinya kumpulan thread adalah ukuran tetap sebagai ukuran minimum.