Các chiến lược từ chối trong ThreadPoolExecutor

Khi hàng đợi tác vụ đầy và số luồng trong ThreadPoolExecutor đã đạt đến giới hạn maximumPoolSize, bất kỳ tác vụ mới nào được gửi vào sẽ bị từ chối. Cơ chế xử lý các tác vụ bị từ chối này được xác định bởi chiến lược từ chối (Rejected Execution Handler).

Trong constructor của ThreadPoolExecutor, nếu không chỉ định rõ handler, hệ thống sẽ sử dụng chiến lược mặc định:

private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();

1. AbortPolicy

Đây là chiến lược mặc định. Khi tác vụ bị từ chối, một ngoại lệ RejectedExecutionException sẽ được ném ra. Phù hợp với các hệ thống yêu cầu độ tin cậy cao, nơi việc phát hiện lỗi kịp thời là quan trọng.

2. DiscardPolicy

Tác vụ bị từ chối sẽ bị loại bỏ mà không có bất kỳ thông báo hay ngoại lệ nào. Chiến lược này thích hợp cho các tác vụ không quan trọng, có thể bỏ qua mà không ảnh hưởng đến hệ thống.

3. DiscardOldestPolicy

Hệ thống sẽ loại bỏ tác vụ lâu nhất trong hàng đợi (tức là tác vụ đầu tiên), sau đó thử lại việc gửi tác vụ mới vào hàng đợi. Lưu ý rằng điều này không đảm bảo tác vụ mới sẽ được chấp nhận — nếu vẫn không đủ chỗ, quá trình này có thể lặp lại hoặc cuối cùng vẫn thất bại.

4. CallerRunsPolicy

Tác vụ bị từ chối sẽ được thực thi trực tiếp bởi luồng gọi (caller thread) — tức là luồng đang gửi tác vụ vào pool. Điều này làm chậm quá trình gửi tác vụ nhưng giúp tránh mất dữ liệu và có thể hữu ích khi tải đột biến.

Để minh họa cách áp dụng các chiến lược này, xét ví dụ sau:

public class TaskRejectionDemo {
    public static void main(String[] args) {
        ThreadFactory factory = r -> new Thread(r, "worker-");
        RejectedExecutionHandler policy = new DiscardPolicy();

        ThreadPoolExecutor executor = new ThreadPoolExecutor(
            2,                    // corePoolSize
            4,                    // maximumPoolSize
            100,                  // keepAliveTime
            TimeUnit.MILLISECONDS,
            new ArrayBlockingQueue<>(3), // queue capacity = 3
            factory,
            policy
        );

        for (int i = 0; i < 20; i++) {
            executor.execute(() -> {
                System.out.println(Thread.currentThread().getName() + " đang xử lý tác vụ");
                try { Thread.sleep(200); } catch (InterruptedException e) { }
            });
        }

        executor.shutdown();
    }
}

Với cấu hình trên: tổng số tác vụ có thể xử lý đồng thời là 4 (luồng tối đa) + 3 (hàng đợi) = 7. Các tác vụ từ thứ 8 trở đi sẽ bị xử lý theo DiscardPolicy — tức là bị loại bỏ thầm lặng.

Nếu thay DiscardPolicy bằng CallerRunsPolicy, các tác vụ vượt quá khả năng sẽ được thực thi ngay tại luồng chính (main), làm chậm vòng lặp gửi tác vụ nhưng đảm bảo không mất tác vụ nào.

Thẻ: Java ThreadPoolExecutor Concurrency

Đăng vào ngày 26 tháng 7 lúc 21:27