Executors.newCachedThreadPool () ile Executors.newFixedThreadPool () karşılaştırması


Yanıtlar:


202

Dokümanların bu iki fonksiyonun farkını ve kullanımını oldukça iyi açıkladığını düşünüyorum:

newFixedThreadPool

Paylaşılan sınırsız kuyrukta çalışan sabit sayıda iş parçacığını yeniden kullanan bir iş parçacığı havuzu oluşturur. Herhangi bir noktada, en fazla nThreads iş parçacığı etkin işleme görevleri olacaktır. Tüm iş parçacıkları etkinken ek görevler gönderilirse, iş parçacığı kullanılabilir olana kadar kuyrukta beklerler. Kapatma öncesinde yürütme sırasındaki bir hata nedeniyle herhangi bir iş parçacığı sonlanırsa, sonraki görevleri yürütmek için gerekirse yeni bir iş parçacığı yerini alır. Havuzdaki iş parçacıkları, tamamen kapatılana kadar var olur.

newCachedThreadPool

Gerektiğinde yeni iş parçacıkları oluşturan ancak önceden oluşturulmuş iş parçacıklarını kullanılabilir olduğunda yeniden kullanacak bir iş parçacığı havuzu oluşturur. Bu havuzlar genellikle kısa ömürlü birçok zaman uyumsuz görevi yürüten programların performansını artıracaktır. Yürütme çağrıları, varsa önceden oluşturulmuş iş parçacıklarını yeniden kullanır. Mevcut bir iş parçacığı yoksa, yeni bir iş parçacığı oluşturulur ve havuza eklenir. Altmış saniye kullanılmayan dişler sonlandırılır ve önbellekten kaldırılır. Bu nedenle, yeterince uzun süre kullanılmayan bir havuz herhangi bir kaynak tüketmeyecektir. ThreadPoolExecutor yapıcıları kullanılarak benzer özelliklere sahip ancak farklı ayrıntılara sahip havuzların (örneğin, zaman aşımı parametreleri) oluşturulabileceğini unutmayın.

Kaynaklar açısından, newFixedThreadPooltüm iş parçacıkları açıkça sonlandırılana kadar çalışmaya devam edecektir. Gelen newCachedThreadPoolaltmış saniye boyunca kullanılmamış olan konular sonlandırılır ve önbellekten kaldırılır.

Bu göz önüne alındığında, kaynak tüketimi duruma çok bağlı olacaktır. Örneğin, çok sayıda uzun süren göreviniz varsa önerebilirim FixedThreadPool. CachedThreadPoolBelgeler gelince , "Bu havuzlar tipik olarak kısa süreli asenkron görevleri yürüten programların performansını artıracaktır" diyor.


1
evet i docs geçti ... sorun ... fixedThreadPool yetersiz bellek hatası @ 3 iş parçacığı neden oluyor .. nerede cachedPool dahili olarak sadece tek bir iş parçacığı oluşturuyor .. yığın boyutunu artırırken ben aynı alıyorum hem performans ... orada eksik başka bir şey var mı!
hakish

1
ThreadPool'a herhangi bir Threadfactory sağlıyor musunuz? Benim tahminim çöp toplanmayan iş parçacığı bazı devlet depolamak olabilir. Değilse, programınız yığın sınırı boyutuna o kadar yakın çalışıyor ki, 3 iş parçacığının oluşturulmasıyla OutOfMemory'ye neden oluyor. Ayrıca, cachedPool dahili olarak yalnızca tek bir iş parçacığı oluşturuyorsa, bu mümkün olan görevlerinizin senkronize çalıştığını gösterir.
bruno conde

@brunoconde gibi dışarı @Louis F. noktaları newCachedThreadPoolbazı neden olabilir ciddi Eğer tüm kontrolünü bırakın çünkü sorunları thread poolne zaman ve hizmet aynı başkalarıyla çalışıyor konak diğerleri nedeniyle uzun süredir CPU bekleyen için çökmesine neden olabilir. Bu yüzden newFixedThreadPoolbu tür senaryoda daha güvenli olabileceğini düşünüyorum . Ayrıca bu yazı , aralarındaki en göze çarpan farklılıkları açıklığa kavuşturuyor.
18'de

75

Sadece diğer cevapları tamamlamak için, Etkili Java, 2. Baskı, Joshua Bloch, bölüm 10, Madde 68'den alıntı yapmak istiyorum:

"Belirli bir uygulama için infaz hizmeti seçimi zor olabilir. Eğer bir yazıyorsanız küçük bir program veya bir hafif yüklü sunucu kullanarak, Executors.new- CachedThreadPool olduğu genellikle iyi bir seçim , genellikle hiçbir yapılandırma talepleri gibi“yapar doğru şey." Ancak, önbelleğe alınmış bir iş parçacığı havuzu, çok yüklü bir üretim sunucusu için iyi bir seçim değildir !

Bir de önbelleğe parçacığı havuzu , gönderilen görevler sıraya değildir ama hemen yürütülmesi için bir iş parçacığı devredilmiştir. Hiçbir iş parçacığı yoksa, yeni bir iş parçacığı oluşturulur . Bir sunucu tüm CPU'larının tamamen kullanıldığı ve daha fazla görev geldiğinde o kadar fazla yüklüyse, daha fazla iş parçacığı oluşturulur ve bu da yalnızca konuları daha da kötüleştirir.

Bu nedenle, çok yüklü bir üretim sunucusunda , maksimum sayıda denetim için sabit sayıda iş parçacığı içeren bir havuz sağlayan Executors.newFixedThreadPool'u veya doğrudan ThreadPoolExecutor sınıfını kullanarak çok daha iyi durumda olursunuz . "


15

Kaynak koda bakarsanız, göreceksiniz, onlar ThreadPoolExecutor çağırıyor . dahili olarak ve özelliklerini ayarlama. Gereksiniminizi daha iyi kontrol edebilmek için bir tane oluşturabilirsiniz.

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>());
}

1
Tam olarak, aklı başında bir üst limite sahip önbelleğe alınmış bir iplik yürütücü ve 5-10 dakika boşta biçme çoğu durumda sadece mükemmel.
Agoston Horvath

12

Sınırlandırılmamış / Çalıştırılabilir görev kuyruğundan endişe etmiyorsanız , bunlardan birini kullanabilirsiniz. Bruno önerdiği gibi, ben de tercih newFixedThreadPooletmeknewCachedThreadPool bu iki bitti.

Ancak ThreadPoolExecutor , newFixedThreadPoolveyanewCachedThreadPool

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

Avantajları:

  1. BlockingQueue boyutu üzerinde tam denetime sahipsiniz . Önceki iki seçenekten farklı olarak sınırsız değildir. Sistemde beklenmedik türbülans olduğunda bekleyen Callable / Runnable görevlerinin büyük bir yığını nedeniyle bellek yetersiz hatası alamayacağım.

  2. Özel Reddetme işleme ilkesini uygulayabilir VEYA ilkelerden birini kullanabilirsiniz:

    1. Varsayılan olarak ThreadPoolExecutor.AbortPolicy, işleyici reddetme üzerine bir çalışma zamanı RejectedExecutionException özel durumu atar.

    2. İçinde ThreadPoolExecutor.CallerRunsPolicy, çağıran iş parçacığı yürütmek kendisi görevi çalıştırır. Bu, yeni görevlerin gönderilme oranını yavaşlatacak basit bir geri bildirim kontrol mekanizması sağlar.

    3. 'De ThreadPoolExecutor.DiscardPolicy, yürütülemeyen bir görev basitçe bırakılır.

    4. Gelen ThreadPoolExecutor.DiscardOldestPolicyinfaz kapatıldı değilse, çalışma sıranın başında görev düşürülür ve sonra yürütme denenir (bu tekrarlanmalıdır neden yine başarısız hangi.)

  3. Aşağıdaki kullanım durumları için özel bir İş parçacığı fabrikası uygulayabilirsiniz:

    1. Daha açıklayıcı bir iş parçacığı adı ayarlamak için
    2. İş parçacığı arka plan programı durumunu ayarlamak için
    3. İplik önceliğini ayarlamak için

11

Bu doğru, Executors.newCachedThreadPool()birden fazla istemciye ve eşzamanlı isteklere hizmet veren sunucu kodu için mükemmel bir seçim değil.

Neden? Temel olarak iki (ilgili) sorun var:

  1. Sınırsızdır, yani herkesin hizmete daha fazla iş enjekte ederek (DoS saldırısı) JVM'nizi sakatlaması için kapıyı açtığınız anlamına gelir. İş parçacıkları önemsiz miktarda bellek tüketir ve devam eden çalışmalarına göre bellek tüketimini arttırır, bu nedenle bir sunucuyu bu şekilde devirmek oldukça kolaydır (başka devre kesiciler yoksa).

  2. Sınırsız problem, Yürütücünün bir tarafından önlenmesi gerçeği ile daha da artmaktadır, SynchronousQueuebu da görev veren ve iş parçacığı havuzu arasında doğrudan bir aktarım olduğu anlamına gelir. Mevcut tüm evreler meşgulse, her yeni görev yeni bir iş parçacığı oluşturur. Bu genellikle sunucu kodu için kötü bir stratejidir. CPU doygun hale geldiğinde, mevcut görevlerin tamamlanması daha uzun sürer. Ancak daha fazla görev gönderiliyor ve daha fazla iş parçacığı oluşturuluyor, bu nedenle görevlerin tamamlanması daha uzun sürüyor. CPU doygun olduğunda, sunucunun ihtiyacı olan şey kesinlikle daha fazla iş parçacığı değildir.

İşte önerilerim:

Sabit boyutlu bir iş parçacığı havuzu Executor.newFixedThreadPool veya bir ThreadPoolExecutor kullanın. belirli bir maksimum iş parçacığı sayısı ile;


6

ThreadPoolExecutorSınıf birçok döndürülür uygulayıcıları için temel uygulamasıdır Executorsfabrika yöntemlerle. Öyleyse Sabit ve Önbelleğe Alınmış iş parçacığı havuzlarına ThreadPoolExecutorbakış açısından yaklaşalım.

ThreadPoolExecutor

Ana yapıcısı bu sınıfın aşağıdaki gibidir:

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

Çekirdek Havuz Boyutu

corePoolSizeHedef iş parçacığı havuzu minimum boyutunu belirler. Yürütülecek görev olmasa bile, uygulama bu boyutta bir havuz tutacaktır.

Maksimum Havuz Boyutu

maximumPoolSizeBir kez etkin olabilir parçacığı sayısıdır.

İş parçacığı havuzu büyüdükten ve corePoolSizeeşik değerden daha büyük hale geldikten sonra, yürütücü boştaki iş parçacıklarını sonlandırabilir ve corePoolSizeyeniden başlayabilir . Eğer allowCoreThreadTimeOutdoğruysa, yürütücü keepAliveTimeeşik değerinden daha boştalarsa çekirdek havuz iş parçacıklarını sonlandırabilir .

Sonuç olarak, iş parçacıkları keepAliveTimeeşik değerinden daha boşta kalırsa , bunlara herhangi bir talep olmadığından sonlandırılabilirler.

Kuyruk

Yeni bir görev geldiğinde ve tüm temel iş parçacıkları işgal edildiğinde ne olur? Yeni görevler bu BlockingQueue<Runnable>örneğin içinde sıraya alınır . Bir iş parçacığı serbest kaldığında, sıraya alınan görevlerden biri işlenebilir.

BlockingQueueJava'da arayüzün farklı uygulamaları vardır , bu nedenle aşağıdaki gibi farklı kuyruk yaklaşımları uygulayabiliriz:

  1. Sınırlı Kuyruk : Yeni görevler, sınırlı görev kuyruğunda sıraya alınır.

  2. Sınırsız Sıra : Sınırsız bir görev kuyruğunda yeni görevler sıraya alınır. Böylece bu kuyruk yığın boyutunun izin verdiği ölçüde büyüyebilir.

  3. Senkron Handoff : SynchronousQueueYeni görevleri sıralamak için de kullanabiliriz . Bu durumda, yeni bir görevi sıraya koyarken, başka bir iş parçacığının o görevi zaten beklemesi gerekir.

İş Teslimi

İşte nasıl ThreadPoolExecutoryeni bir görev yürütür:

  1. İş corePoolSizeparçacıklarından daha azı çalışıyorsa, verilen görev ilk işi olarak yeni bir iş parçacığı başlatmaya çalışır.
  2. Aksi takdirde, BlockingQueue#offeryöntemi kullanarak yeni görevi kuyruğa almaya çalışır . offerKuyruk döndürür hemen dolu ve eğer yöntem engellemez false.
  3. Yeni görevi kuyruğa alamazsa (örn. Geri offerdöner false), bu görev ilk işi olarak iş parçacığı havuzuna yeni bir iş parçacığı eklemeye çalışır.
  4. Yeni iş parçacığı eklenemezse, yürütücü kapatılır veya doyurulur. Her iki durumda da, yeni görev sağlanan kullanılarak reddedilir RejectedExecutionHandler.

Sabit ve önbelleğe alınmış iş parçacığı havuzları arasındaki temel fark şu üç faktöre dayanır:

  1. Çekirdek Havuz Boyutu
  2. Maksimum Havuz Boyutu
  3. Kuyruk
+ ----------- + ----------- + ------------------- + ----- ---------------------------- +
| Havuz Tipi | Çekirdek Boyutu | Maksimum Boyut | Kuyruk Stratejisi |
+ ----------- + ----------- + ------------------- + ----- ---------------------------- +
| Sabit | n (sabit) | n (sabit) | Sınırsız `LinkedBlockingQueue` |
+ ----------- + ----------- + ------------------- + ----- ---------------------------- +
| Önbellek | 0 | Tamsayı.MAX_VALUE | `SynchronousQueue` |
+ ----------- + ----------- + ------------------- + ----- ---------------------------- +


Sabit İplik Havuzu


Nasıl Excutors.newFixedThreadPool(n)çalışır:

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

Gördüğün gibi:

  • İş parçacığı havuzu boyutu sabittir.
  • Yüksek talep varsa büyümeyecektir.
  • İplikler bir süre boşta kalırsa, küçülmez.
  • Tüm bu iş parçacıklarının uzun süren bazı görevlerle meşgul olduğunu ve varış oranının hala oldukça yüksek olduğunu varsayalım. Yürütücü sınırsız bir kuyruk kullandığından, yığının büyük bir bölümünü tüketebilir. Yeterince talihsiz olmakla karşılaşabiliriz OutOfMemoryError.

Birini veya diğerini ne zaman kullanmalıyım? Hangi strateji kaynak kullanımı açısından daha iyidir?

Kaynak yönetimi amacıyla eşzamanlı görev sayısını sınırlayacağımızda, sabit boyutlu bir iş parçacığı havuzu iyi bir aday gibi görünüyor .

Örneğin, web sunucusu isteklerini işlemek için bir yürütücü kullanacaksak, sabit bir yürütücü istek patlamalarını daha makul bir şekilde işleyebilir.

Daha da iyi kaynak yönetimi için, ThreadPoolExecutorsınırlı BlockingQueue<T>uygulamalarla makul bir özelliğin oluşturulması özel olarak önerilir RejectedExecutionHandler.


Önbelleğe Alınmış İş Parçacığı Havuzu


Nasıl Executors.newCachedThreadPool()çalışır:

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

Gördüğün gibi:

  • İş parçacığı havuzu sıfır iş parçacığı için büyüyebilir Integer.MAX_VALUE. Pratik olarak, iş parçacığı havuzu sınırsızdır.
  • Herhangi bir iş parçacığı 1 dakikadan fazla boşta kalırsa, sonlandırılabilir. Böylece iplikler çok fazla boş kalırsa havuz büzüşebilir.
  • Yeni bir görev geldiğinde ayrılan tüm iş parçacıkları işgal edilirse, yeni bir iş parçacığı oluşturur, çünkü yeni bir iş sunar, çünkü SynchronousQueueher zaman yeni bir iş sunar .

Birini veya diğerini ne zaman kullanmalıyım? Hangi strateji kaynak kullanımı açısından daha iyidir?

Öngörülebilir kısa süreli görevleriniz olduğunda kullanın.



1

Bazı hızlı testler yapıyorum ve aşağıdaki bulgularım var:

1) SynchronousQueue kullanıyorsanız:

Dişler maksimum boyuta ulaştıktan sonra, aşağıdaki gibi istisnalar dışında tüm yeni çalışmalar reddedilecektir.

"Main" iş parçacığında kural dışı durum java.util.concurrent.RejectedExecutionException: Görev java.util.concurrent.FutureTask@3fee733d java.util.concurrent.RejectedExecutionException: Görev java.util.concurrent.ThreadPoolExecutor@5acf9800 [Çalışıyor, havuz boyutu = 3, etkin iş parçacığı = 3, sıraya alınmış görevler = 0, tamamlanan görev = 0]

java.util.concurrent.ThreadPoolExecutor $ AbortPolicy.rejectedExecution'da (ThreadPoolExecutor.java:2047)

2) LinkedBlockingQueue kullanılıyorsa:

Dişler asla minimum boyuttan maksimum boyuta yükselmez, yani iplik havuzu minimum boyut olarak sabit boyuttadır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.