ThreadPoolExecutor
Havuz oluşturulduktan sonra bir çekirdek havuz boyutunu farklı bir sayıya yeniden boyutlandırmaya çalışırsam , o zaman aralıklı olarak, bazı görevler RejectedExecutionException
hiçbir zaman birden fazla queueSize + maxPoolSize
görev göndermeme rağmen reddedildiği bir sorunla karşılaşıyorum .
Çözmeye çalıştığım sorun ThreadPoolExecutor
, iş parçacığı havuzu kuyruğunda oturan bekleyen yürütmelere dayalı olarak çekirdek iş parçacıklarını yeniden boyutlandırmak genişletmektir . Varsayılan olarak bir sadece sıra dolu olduğunda ThreadPoolExecutor
yeni bir yaratacaktır çünkü buna ihtiyacım var Thread
.
İşte sorunu gösteren küçük bir bağımsız Pure Java 8 programı.
import static java.lang.Math.max;
import static java.lang.Math.min;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolResizeTest {
public static void main(String[] args) throws Exception {
// increase the number of iterations if unable to reproduce
// for me 100 iterations have been enough
int numberOfExecutions = 100;
for (int i = 1; i <= numberOfExecutions; i++) {
executeOnce();
}
}
private static void executeOnce() throws Exception {
int minThreads = 1;
int maxThreads = 5;
int queueCapacity = 10;
ThreadPoolExecutor pool = new ThreadPoolExecutor(
minThreads, maxThreads,
0, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(queueCapacity),
new ThreadPoolExecutor.AbortPolicy()
);
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
scheduler.scheduleAtFixedRate(() -> resizeThreadPool(pool, minThreads, maxThreads),
0, 10, TimeUnit.MILLISECONDS);
CompletableFuture<Void> taskBlocker = new CompletableFuture<>();
try {
int totalTasksToSubmit = queueCapacity + maxThreads;
for (int i = 1; i <= totalTasksToSubmit; i++) {
// following line sometimes throws a RejectedExecutionException
pool.submit(() -> {
// block the thread and prevent it from completing the task
taskBlocker.join();
});
// Thread.sleep(10); //enabling even a small sleep makes the problem go away
}
} finally {
taskBlocker.complete(null);
scheduler.shutdown();
pool.shutdown();
}
}
/**
* Resize the thread pool if the number of pending tasks are non-zero.
*/
private static void resizeThreadPool(ThreadPoolExecutor pool, int minThreads, int maxThreads) {
int pendingExecutions = pool.getQueue().size();
int approximateRunningExecutions = pool.getActiveCount();
/*
* New core thread count should be the sum of pending and currently executing tasks
* with an upper bound of maxThreads and a lower bound of minThreads.
*/
int newThreadCount = min(maxThreads, max(minThreads, pendingExecutions + approximateRunningExecutions));
pool.setCorePoolSize(newThreadCount);
pool.prestartAllCoreThreads();
}
}
QueueCapacity + maxThreads öğesinden daha fazlasını hiç göndermezsem neden havuz hiç RejectedExecutionException özel durumu oluşturmalıdır. Ben max iş parçacığı asla ThreadPoolExecutor'ın tanımı ile değiştirmiyorum, ya bir iş parçacığı ya da kuyruk görev uygun olmalıdır.
Tabii ki, ben asla havuzu yeniden boyutlandırmak, o zaman iş parçacığı havuzu herhangi bir başvuru reddetmez. Bu ayrıca hata ayıklamak zordur çünkü gönderilere herhangi bir gecikme eklemek sorunu ortadan kaldırır.
RejectedExecutionException düzeltmek için herhangi bir işaretçiler?
ThreadPoolExecutor
muhtemelen kötü bir fikirdir ve bu durumda da mevcut kodu değiştirmeniz gerekmez mi? Gerçek kodunuzun yürütücüye nasıl eriştiğine dair bir örnek vermeniz iyi olur. Eğer özel ThreadPoolExecutor
(yani değil ExecutorService
) için birçok yöntem kullandıysanız şaşırırdım .
ExecutorService
yeniden boyutlandırma nedeniyle gönderilemeyen görevleri yeniden gönderen mevcut bir paketi sararak kendi uygulamanızı sağlamıyorsunuz ?