Aşağıdaki sınıf bir ThreadPoolExecutor etrafına sarılır ve engellemek için bir Semafor kullanır, ardından iş kuyruğu dolu olur:
public final class BlockingExecutor {
private final Executor executor;
private final Semaphore semaphore;
public BlockingExecutor(int queueSize, int corePoolSize, int maxPoolSize, int keepAliveTime, TimeUnit unit, ThreadFactory factory) {
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
this.executor = new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime, unit, queue, factory);
this.semaphore = new Semaphore(queueSize + maxPoolSize);
}
private void execImpl (final Runnable command) throws InterruptedException {
semaphore.acquire();
try {
executor.execute(new Runnable() {
@Override
public void run() {
try {
command.run();
} finally {
semaphore.release();
}
}
});
} catch (RejectedExecutionException e) {
// will never be thrown with an unbounded buffer (LinkedBlockingQueue)
semaphore.release();
throw e;
}
}
public void execute (Runnable command) throws InterruptedException {
execImpl(command);
}
}
Bu sarmalayıcı sınıfı, Brian Goetz tarafından yazılan Java Eşzamanlılığı kitabında verilen çözüme dayanmaktadır. Kitaptaki çözüm yalnızca iki yapıcı parametresi alır: Executor
semafor için kullanılan bir ve bir sınır. Bu, Fixpoint tarafından verilen cevapta gösterilmiştir. Bu yaklaşımla ilgili bir sorun var: havuz iş parçacıklarının meşgul olduğu, kuyruğun dolu olduğu bir duruma girebilir, ancak semafor bir izin yayınladı. (semaphore.release()
nihayet blokta). Bu durumda, yeni bir görev henüz serbest bırakılan izni alabilir, ancak görev kuyruğu dolu olduğu için reddedilir. Tabii ki bu senin istediğin bir şey değil; bu durumda engellemek istiyorsunuz.
Bunu çözmek için , JCiP'nin açıkça belirttiği gibi, sınırsız bir kuyruk kullanmalıyız . Semafor, sanal kuyruk boyutunun etkisini veren bir koruma görevi görür. Bu, ünitenin maxPoolSize + virtualQueueSize + maxPoolSize
görevleri içermesinin olası yan etkisine sahiptir . Neden? semaphore.release()
Nihayet bloğundaki yüzünden
. Tüm havuz iş parçacıkları bu ifadeyi aynı anda çağırırsa, maxPoolSize
izinler serbest bırakılarak aynı sayıda görevin birime girmesine izin verilir. Sınırlı bir kuyruk kullansaydık, yine de dolu olurdu ve bu da reddedilmiş bir görevle sonuçlanırdı. Şimdi, bunun yalnızca bir havuz iş parçacığı neredeyse bittiğinde gerçekleştiğini bildiğimiz için, bu bir sorun değil. Havuz iş parçacığının engellenmeyeceğini biliyoruz, bu nedenle kısa süre içinde kuyruktan bir görev alınacaktır.
Yine de sınırlı bir kuyruk kullanabilirsiniz. Sadece boyutunun eşit olduğundan emin olun virtualQueueSize + maxPoolSize
. Daha büyük boyutlar işe yaramaz, semafor daha fazla öğenin içeri girmesine izin vermez. Daha küçük boyutlar, reddedilen görevlerle sonuçlanır. Boyut azaldıkça görevlerin reddedilme şansı artar. Örneğin, maxPoolSize = 2 ve virtualQueueSize = 5 olan sınırlı bir yürütücü istediğinizi varsayalım. Ardından 5 + 2 = 7 izinli ve gerçek kuyruk boyutu 5 + 2 = 7 olan bir semafor alın. Ünitedeki gerçek görev sayısı 2 + 5 + 2 = 9'dur. Yürütücü dolu olduğunda (kuyrukta 5 görev, 2 iş parçacığı havuzunda, yani 0 izin kullanılabilir) ve TÜM havuz iş parçacıkları izinlerini serbest bıraktığında, gelen görevler tarafından tam olarak 2 izin alınabilir.
Şimdi, JCiP çözümünün kullanımı, tüm bu kısıtlamaları (sınırsız kuyruk veya bu matematik kısıtlamalarıyla sınırlandırılmış vb.) Zorlamadığı için kullanmak biraz zahmetlidir. Sanırım bu, halihazırda mevcut olan parçalara dayalı olarak yeni iş parçacığı güvenli sınıfları nasıl oluşturabileceğinizi göstermek için iyi bir örnek olarak hizmet ediyor, ancak tam gelişmiş, yeniden kullanılabilir bir sınıf olarak değil. İkincisinin yazarın niyeti olduğunu sanmıyorum.