Saya mengalami masalah di mana jika saya mencoba untuk mengubah ukuran ukuran ThreadPoolExecutor
inti kolam ke nomor yang berbeda setelah kolam telah dibuat, maka sebentar-sebentar, beberapa tugas ditolak dengan RejectedExecutionException
meskipun saya tidak pernah mengirimkan lebih dari queueSize + maxPoolSize
jumlah tugas.
Masalah yang saya coba pecahkan adalah untuk memperluas ThreadPoolExecutor
yang mengubah ukuran utas inti berdasarkan eksekusi yang tertunda dalam antrian kumpulan thread. Saya memerlukan ini karena secara default a ThreadPoolExecutor
akan membuat yang baru Thread
hanya jika antrian penuh.
Berikut ini adalah program Java 8 mandiri kecil mandiri yang menunjukkan masalah.
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();
}
}
Mengapa kumpulan ini harus pernah melempar RejectedExecutionException jika saya tidak pernah mengirimkan lebih dari queueCapacity + maxThreads. Saya tidak pernah mengubah utas maksimal, jadi menurut definisi ThreadPoolExecutor, ia harus mengakomodasi tugas dalam Utas atau ke antrian.
Tentu saja, jika saya tidak pernah mengubah ukuran pool, maka pool thread tidak pernah menolak pengiriman. Ini juga sulit untuk di-debug karena menambahkan segala jenis keterlambatan dalam pengiriman membuat masalah hilang.
Adakah petunjuk tentang cara memperbaiki RejectedExecutionException?
ThreadPoolExecutor
mungkin adalah ide yang buruk, dan tidakkah Anda perlu mengubah kode yang ada dalam kasus ini juga? Sebaiknya Anda memberikan beberapa contoh bagaimana kode aktual Anda mengakses pelaksana. Saya akan terkejut jika menggunakan banyak metode khusus ThreadPoolExecutor
(yaitu tidak di ExecutorService
).
ExecutorService
dengan membungkus yang sudah ada, yang mengirimkan kembali tugas-tugas yang gagal diajukan karena pengubahan ukuran?