Executors.newCachedThreadPool () versus Executors.newFixedThreadPool ()

Jawaban:

202

Saya pikir dokumen menjelaskan perbedaan dan penggunaan kedua fungsi ini dengan cukup baik:

newFixedThreadPool

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.

newCachedThreadPool

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 newCachedThreadPoolThread 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".

bruno conde
sumber
1
ya saya telah membaca dokumen..masalahnya adalah ... fixedThreadPool menyebabkan kesalahan memori @ 3 utas .. dimana cachedPool secara internal hanya membuat satu utas .. pada peningkatan ukuran heap saya mendapatkan hal yang sama kinerja untuk keduanya .. adakah yang lain yang saya lewatkan !!
hakish
1
Apakah Anda menyediakan Threadfactory ke ThreadPool? Dugaan saya adalah bahwa mungkin menyimpan beberapa keadaan di utas yang tidak dikumpulkan sampah. Jika tidak, mungkin program Anda berjalan sangat dekat dengan ukuran batas tumpukan sehingga dengan penciptaan 3 utas itu menyebabkan OutOfMemory. Juga, jika cachedPool secara internal hanya membuat satu utas, maka ini mungkin menunjukkan bahwa tugas Anda berjalan dengan sinkronisasi.
Bruno conde
@brunoconde Sama seperti @Louis F. menunjukkan bahwa newCachedThreadPoolmungkin menyebabkan beberapa masalah serius karena Anda meninggalkan semua kontrol ke thread pooldan ketika layanan bekerja dengan orang lain di host yang sama , yang mungkin menyebabkan yang lain macet karena menunggu CPU lama. Jadi saya pikir newFixedThreadPoolbisa lebih aman dalam skenario semacam ini. Juga posting ini menjelaskan perbedaan paling menonjol di antara mereka.
Dengar
75

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. "

Louis F.
sumber
15

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>());
}
krmanish007
sumber
1
Tepatnya, eksekutor thread dalam cache dengan batas atas waras dan berkata, 5-10 menit menuai menganggur sangat cocok untuk sebagian besar kesempatan.
Agoston Horvath
12

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 newFixedThreadPooluntuknewCachedThreadPool lebih dari dua ini.

Tetapi ThreadPoolExecutor menyediakan fitur yang lebih fleksibel dibandingkan dengan salah satu newFixedThreadPoolataunewCachedThreadPool

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, 
TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
RejectedExecutionHandler handler)

Keuntungan:

  1. 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.

  2. Anda dapat menerapkan kebijakan penanganan Penolakan khusus ATAU menggunakan salah satu kebijakan:

    1. Secara default ThreadPoolExecutor.AbortPolicy, pawang melempar runtime RejectedExecutionException saat penolakan.

    2. 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.

    3. Dalam ThreadPoolExecutor.DiscardPolicy, tugas yang tidak dapat dieksekusi dijatuhkan begitu saja.

    4. Di ThreadPoolExecutor.DiscardOldestPolicy, jika pelaksana tidak dimatikan, tugas di kepala antrian pekerjaan dijatuhkan, dan kemudian eksekusi dicoba (yang bisa gagal lagi, menyebabkan ini terulang.)

  3. Anda dapat menerapkan pabrik Thread khusus untuk kasus penggunaan di bawah ini:

    1. Untuk menetapkan nama utas yang lebih deskriptif
    2. Untuk mengatur status daemon utas
    3. Untuk menetapkan prioritas utas
Ravindra babu
sumber
11

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:

  1. 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).

  2. Masalah yang tidak terbatas diperburuk oleh fakta bahwa Pelaksana digawangi oleh SynchronousQueueyang 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;

Prashant Gautam
sumber
6

The ThreadPoolExecutorkelas adalah implementasi dasar untuk pelaksana yang kembali dari banyak Executorsmetode pabrik. Jadi mari kita mendekati kumpulan thread Tetap dan Cached dariThreadPoolExecutor perspektif.

ThreadPoolExecutor

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
)

Ukuran Kolam Inti

The corePoolSizemenentukan ukuran minimum dari kolam renang sasaran benang.Implementasi akan mempertahankan kumpulan ukuran itu bahkan jika tidak ada tugas untuk dieksekusi.

Ukuran Kolam Maksimum

Ini maximumPoolSizeadalah jumlah maksimum utas yang dapat aktif sekaligus.

Setelah kumpulan thread tumbuh dan menjadi lebih besar dari corePoolSizeambang, pelaksana dapat menghentikan utas menganggur dan mencapai ke corePoolSizelagi. Jika allowCoreThreadTimeOutbenar, 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 dalamnyaBlockingQueue<Runnable> instance itu. Ketika utas menjadi gratis, salah satu tugas yang antri dapat diproses.

Ada implementasi yang berbeda dari BlockingQueueantarmuka di Jawa, sehingga kami dapat menerapkan pendekatan antrian yang berbeda seperti:

  1. Bounded Queue : Tugas baru akan diantrikan di dalam antrian tugas yang dibatasi.

  2. Antrian Tanpa Batas : Tugas baru akan di-antri di dalam antrian tugas tidak terikat. Jadi antrian ini dapat tumbuh sebanyak ukuran tumpukan memungkinkan.

  3. Synchronous Handoff : Kita juga dapat menggunakan SynchronousQueueuntuk mengantri tugas-tugas baru. Dalam hal itu, ketika mengantri tugas baru, utas lain harus sudah menunggu tugas itu.

Pengajuan Pekerjaan

Begini cara ThreadPoolExecutormenjalankan tugas baru:

  1. Jika kurang dari corePoolSize utas berjalan, cobalah untuk memulai utas baru dengan tugas yang diberikan sebagai pekerjaan pertama.
  2. Jika tidak, ia mencoba untuk melakukan tugas baru menggunakan BlockingQueue#offermetode ini. The offerMetode tidak akan memblokir jika antrian penuh dan segera kembali false.
  3. Jika gagal mengantri tugas baru (yaitu offerkembali false), maka ia mencoba untuk menambahkan utas baru ke kumpulan utas dengan tugas ini sebagai pekerjaan pertama.
  4. Jika gagal menambahkan utas baru, maka pelaksana dimatikan atau jenuh. Either way, tugas baru akan ditolak menggunakan yang disediakan RejectedExecutionHandler.

Perbedaan utama antara kolam ulir tetap dan cache bermuara pada tiga faktor ini:

  1. Ukuran Kolam Inti
  2. Ukuran Kolam Maksimum
  3. Antrian
+ ----------- + ----------- + ------------------- + ----- ---------------------------- +
| Jenis Kolam | Ukuran Inti | Ukuran Maksimum | Strategi Antrian |
+ ----------- + ----------- + ------------------- + ----- ---------------------------- +
| Diperbaiki | n (tetap) | n (tetap) | `LinkedBlockingQueue` | Tidak Terbatas
+ ----------- + ----------- + ------------------- + ----- ---------------------------- +
| Dalam cache | 0 | Integer.MAX_VALUE | `SynchronousQueue` |
+ ----------- + ----------- + ------------------- + ----- ---------------------------- +


Fixed Thread Pool


Begini cara 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:

  • Ukuran kumpulan utas sudah ditetapkan.
  • Jika ada permintaan tinggi, itu tidak akan tumbuh.
  • Jika utas menganggur selama beberapa waktu, itu tidak akan menyusut.
  • Misalkan semua utas diisi dengan beberapa tugas jangka panjang dan tingkat kedatangan masih cukup tinggi. Karena pelaksana menggunakan antrian tanpa batas, ia mungkin menggunakan sebagian besar tumpukan. Cukup disayangkan, kita mungkin mengalami 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 ThreadPoolExecutordengan BlockingQueue<T>implementasi terbatas disertai dengan wajar RejectedExecutionHandler.


Cached Thread Pool


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:

  • Kumpulan utas dapat tumbuh dari nol utas sampai Integer.MAX_VALUE. Secara praktis, kumpulan utas tidak terbatas.
  • Jika ada utas yang menganggur selama lebih dari 1 menit, mungkin akan dihentikan. Jadi kolam bisa menyusut jika utas tetap terlalu banyak menganggur.
  • Jika semua utas yang dialokasikan ditempati saat tugas baru masuk, maka ia membuat utas baru, karena menawarkan tugas baru kepada yang SynchronousQueueselalu 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.

Ali Dehghani
sumber
5

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/ ).

Dhanaraj Durairaj
sumber
1

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.

Mike Lin
sumber