newCachedThreadPool()
karşı newFixedThreadPool()
Birini veya diğerini ne zaman kullanmalıyım? Hangi strateji kaynak kullanımı açısından daha iyidir?
newCachedThreadPool()
karşı newFixedThreadPool()
Birini veya diğerini ne zaman kullanmalıyım? Hangi strateji kaynak kullanımı açısından daha iyidir?
Yanıtlar:
Dokümanların bu iki fonksiyonun farkını ve kullanımını oldukça iyi açıkladığını düşünüyorum:
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.
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, newFixedThreadPool
tüm iş parçacıkları açıkça sonlandırılana kadar çalışmaya devam edecektir. Gelen newCachedThreadPool
altmış 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
. CachedThreadPool
Belgeler gelince , "Bu havuzlar tipik olarak kısa süreli asenkron görevleri yürüten programların performansını artıracaktır" diyor.
newCachedThreadPool
bazı neden olabilir ciddi Eğer tüm kontrolünü bırakın çünkü sorunları thread pool
ne 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 newFixedThreadPool
bu 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.
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 . "
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>());
}
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 newFixedThreadPool
etmeknewCachedThreadPool
bu iki bitti.
Ancak ThreadPoolExecutor , newFixedThreadPool
veyanewCachedThreadPool
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
RejectedExecutionHandler handler)
Avantajları:
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.
Özel Reddetme işleme ilkesini uygulayabilir VEYA ilkelerden birini kullanabilirsiniz:
Varsayılan olarak ThreadPoolExecutor.AbortPolicy
, işleyici reddetme üzerine bir çalışma zamanı RejectedExecutionException özel durumu atar.
İç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.
'De ThreadPoolExecutor.DiscardPolicy
, yürütülemeyen bir görev basitçe bırakılır.
Gelen ThreadPoolExecutor.DiscardOldestPolicy
infaz 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.)
Aşağıdaki kullanım durumları için özel bir İş parçacığı fabrikası uygulayabilirsiniz:
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:
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).
Sınırsız problem, Yürütücünün bir tarafından önlenmesi gerçeği ile daha da artmaktadır, SynchronousQueue
bu 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;
ThreadPoolExecutor
Sınıf birçok döndürülür uygulayıcıları için temel uygulamasıdır Executors
fabrika yöntemlerle. Öyleyse Sabit ve Önbelleğe Alınmış iş parçacığı havuzlarına ThreadPoolExecutor
bakış açısından yaklaşalım.
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
)
corePoolSize
Hedef iş parçacığı havuzu minimum boyutunu belirler. Yürütülecek görev olmasa bile, uygulama bu boyutta bir havuz tutacaktır.
maximumPoolSize
Bir kez etkin olabilir parçacığı sayısıdır.
İş parçacığı havuzu büyüdükten ve corePoolSize
eşik değerden daha büyük hale geldikten sonra, yürütücü boştaki iş parçacıklarını sonlandırabilir ve corePoolSize
yeniden başlayabilir . Eğer allowCoreThreadTimeOut
doğruysa, yürütücü keepAliveTime
eşik değerinden daha boştalarsa çekirdek havuz iş parçacıklarını sonlandırabilir .
Sonuç olarak, iş parçacıkları keepAliveTime
eşik değerinden daha boşta kalırsa , bunlara herhangi bir talep olmadığından sonlandırılabilirler.
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.
BlockingQueue
Java'da arayüzün farklı uygulamaları vardır , bu nedenle aşağıdaki gibi farklı kuyruk yaklaşımları uygulayabiliriz:
Sınırlı Kuyruk : Yeni görevler, sınırlı görev kuyruğunda sıraya alınır.
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.
Senkron Handoff : SynchronousQueue
Yeni 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.
İşte nasıl ThreadPoolExecutor
yeni bir görev yürütür:
corePoolSize
parçacıklarından daha azı çalışıyorsa, verilen görev ilk işi olarak yeni bir iş parçacığı başlatmaya çalışır.BlockingQueue#offer
yöntemi kullanarak yeni görevi kuyruğa almaya çalışır
. offer
Kuyruk döndürür hemen dolu ve eğer yöntem engellemez false
.offer
döner false
), bu görev ilk işi olarak iş parçacığı havuzuna yeni bir iş parçacığı eklemeye çalışır.RejectedExecutionHandler
.Sabit ve önbelleğe alınmış iş parçacığı havuzları arasındaki temel fark şu üç faktöre dayanır:
+ ----------- + ----------- + ------------------- + ----- ---------------------------- + | Havuz Tipi | Çekirdek Boyutu | Maksimum Boyut | Kuyruk Stratejisi | + ----------- + ----------- + ------------------- + ----- ---------------------------- + | Sabit | n (sabit) | n (sabit) | Sınırsız `LinkedBlockingQueue` | + ----------- + ----------- + ------------------- + ----- ---------------------------- + | Önbellek | 0 | Tamsayı.MAX_VALUE | `SynchronousQueue` | + ----------- + ----------- + ------------------- + ----- ---------------------------- +
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:
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, ThreadPoolExecutor
sınırlı BlockingQueue<T>
uygulamalarla makul bir özelliğin oluşturulması özel olarak önerilir RejectedExecutionHandler
.
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:
Integer.MAX_VALUE
. Pratik olarak, iş parçacığı havuzu sınırsızdır.SynchronousQueue
her 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.
NewCachedThreadPool'u yalnızca Javadoc'da belirtildiği gibi kısa ömürlü asenkron görevleriniz olduğunda kullanmalısınız, işlenmesi daha uzun süren görevler gönderirseniz, çok fazla iş parçacığı oluşturursunuz. NewCachedThreadPool'a ( http://rashcoder.com/be-careful-while-using-executors-newcachedthreadpool/ ) daha hızlı bir şekilde uzun süren görevler gönderirseniz% 100 CPU'yu vurabilirsiniz .
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.