Tham số chính: corePoolSize, maximumPoolSize, keepAliveTime, workQueue, threadFactory, RejectedExecutionHandler.
Thứ tự xử lý một task mới hay bị hiểu sai:
1. Đang chạy < corePoolSize → tạo thread mới.
2. Đủ core rồi → đẩy vào workQueue.
3. Queue đầy → mới tạo thêm thread tới maximumPoolSize.
4. Queue đầy và đã đủ max → gọi RejectedExecutionHandler.
Vì bước 3 chỉ xảy ra khi queue đầy, maximumPoolSize vô nghĩa nếu queue không có giới hạn.
Đó chính là vấn đề của Executors.newFixedThreadPool(n): nó dùng LinkedBlockingQueue không giới hạn dung lượng. Khi tải vào nhanh hơn tốc độ xử lý, queue phình theo bộ nhớ cho tới OutOfMemoryError, và không có tín hiệu quá tải nào phát ra trước đó. newCachedThreadPool() ngược lại: maximumPoolSize là Integer.MAX_VALUE, tạo thread không giới hạn.
Cấu hình tường minh cho service thật:
var pool = new ThreadPoolExecutor(
8, 16, 60L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(500), // queue có trần
new ThreadPoolExecutor.CallerRunsPolicy()); // quá tải -> đẩy ngược áp lựcQueue có trần + chính sách từ chối rõ ràng biến quá tải thành lỗi nhìn thấy được thay vì OOM âm thầm.