ThreadPoolExecutor
Daha fazla iş parçacığı başlatılmadan önce kuyruğun sınırlı ve dolu olması gereken yerlerde bu sınırlamayı nasıl aşabilirim.
Sonunda bu sınırlamaya biraz zarif (belki biraz hile) bir çözüm bulduğuma inanıyorum ThreadPoolExecutor
. Halihazırda sıraya alınmış bazı görevler olduğunda LinkedBlockingQueue
geri dönmesi false
için genişletmeyi içerir queue.offer(...)
. Mevcut iş parçacıkları sıraya alınmış görevlere yetişmiyorsa, TPE ek iş parçacıkları ekleyecektir. Havuz zaten maksimum iş parçacığında ise, o RejectedExecutionHandler
zaman çağrılacaktır. Daha sonra put(...)
kuyruğa giren işleyicidir .
Geri offer(...)
dönebilecek false
ve put()
asla engellemeyecek bir kuyruk yazmak kesinlikle gariptir, bu yüzden hack kısmı budur. Ancak bu, TPE'nin kuyruk kullanımıyla iyi çalışıyor, bu yüzden bunu yaparken herhangi bir sorun görmüyorum.
İşte kod:
// extend LinkedBlockingQueue to force offer() to return false conditionally
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>() {
private static final long serialVersionUID = -6903933921423432194L;
@Override
public boolean offer(Runnable e) {
// Offer it to the queue if there is 0 items already queued, else
// return false so the TPE will add another thread. If we return false
// and max threads have been reached then the RejectedExecutionHandler
// will be called which will do the put into the queue.
if (size() == 0) {
return super.offer(e);
} else {
return false;
}
}
};
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1 /*core*/, 50 /*max*/,
60 /*secs*/, TimeUnit.SECONDS, queue);
threadPool.setRejectedExecutionHandler(new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
try {
// This does the actual put into the queue. Once the max threads
// have been reached, the tasks will then queue up.
executor.getQueue().put(r);
// we do this after the put() to stop race conditions
if (executor.isShutdown()) {
throw new RejectedExecutionException(
"Task " + r + " rejected from " + e);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
});
Bu mekanizma ile, görevleri kuyruğa gönderdiğimde, ThreadPoolExecutor
irade:
- Başlangıçta çekirdek boyutuna kadar iş parçacığı sayısını ölçekleyin (burada 1).
- Sıraya sunun. Kuyruk boşsa, mevcut evreler tarafından işlenmek üzere sıraya alınacaktır.
- Kuyrukta zaten 1 veya daha fazla öğe varsa,
offer(...)
yanlış döndürür.
- Yanlış döndürülürse, havuzdaki iş parçacığı sayısını maksimum sayıya ulaşana kadar ölçekleyin (burada 50).
- En fazla ise, o zaman
RejectedExecutionHandler
- Ardından
RejectedExecutionHandler
, görevi FIFO sırasındaki ilk kullanılabilir iş parçacığı tarafından işlenmek üzere kuyruğa koyar.
Yukarıdaki örnek kodumda, kuyruk sınırsız olmasına rağmen, onu sınırlı bir kuyruk olarak da tanımlayabilirsiniz. Örneğin, 1000 kapasite eklerseniz, LinkedBlockingQueue
o zaman:
- konuları maksimuma ölçeklendirin
- sonra 1000 görevle dolana kadar sıraya girin
- daha sonra kuyrukta yer olana kadar arayanı engelleyin.
Ayrıca, offer(...)
içinde
RejectedExecutionHandler
kullanmanız gerekirse , zaman aşımı olarak offer(E, long, TimeUnit)
bunun yerine yöntemi kullanabilirsiniz Long.MAX_VALUE
.
Uyarı:
Eğer görevleri uygulamakla eklenecek bekliyorsanız sonra bu kapatma olmuştur o zaman atma hakkında daha verimli olmasını isteyebilirsiniz, RejectedExecutionException
bizim özel dışına RejectedExecutionHandler
infaz hizmet kapatma olmuştur zaman. @RaduToader'a bunu işaret ettiği için teşekkürler.
Düzenle:
Bu cevaba yönelik başka bir ince ayar, TPE'ye boşta iş parçacığı olup olmadığını sormak ve varsa öğeyi yalnızca kuyruğa almak olabilir. Bunun için gerçek bir sınıf oluşturmanız ve ourQueue.setThreadPoolExecutor(tpe);
üzerine yöntem eklemeniz gerekir.
O zaman offer(...)
yönteminiz şöyle görünebilir:
- Olmadığını kontrol edin
tpe.getPoolSize() == tpe.getMaximumPoolSize()
, bu durumda sadece ara super.offer(...)
.
- Aksi takdirde boşta iş parçacığı olduğu için
tpe.getPoolSize() > tpe.getActiveCount()
arayın super.offer(...)
.
- Aksi takdirde
false
başka bir iş parçacığını çatallamaya geri dönün .
Belki bu:
int poolSize = tpe.getPoolSize();
int maximumPoolSize = tpe.getMaximumPoolSize();
if (poolSize >= maximumPoolSize || poolSize > tpe.getActiveCount()) {
return super.offer(e);
} else {
return false;
}
TPE üzerindeki alma yöntemlerinin, volatile
alanlara eriştikleri veya (olması durumunda getActiveCount()
) TPE'yi kilitledikleri ve iş parçacığı listesinde yürüdükleri için pahalı olduğuna dikkat edin . Ayrıca, burada bir görevin yanlış bir şekilde sıralanmasına veya boşta bir iş parçacığı olduğunda başka bir iş parçacığının çatallanmasına neden olabilecek yarış koşulları vardır.