Bootstrap

拥有拒绝策略的线程池如何设计

好的,下面我将给出一个包含拒绝策略的线程池模拟代码。当任务队列已满且线程池已达到最大线程数时,将使用拒绝策略来处理新提交的任务。拒绝策略可以是抛异常、丢弃任务、调用者执行任务等。

我们将修改之前的线程池代码,使其支持以下功能:

  1. 最大任务队列长度:当任务队列满了,不能再添加新的任务。
  2. 拒绝策略:当任务无法提交时,我们可以选择不同的拒绝策略处理这种情况。
代码实现:带有拒绝策略的线程池

在这里插入图片描述

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.RejectedExecutionException;

class SimpleThreadPoolWithRejectPolicy {
    private final List<Worker> workers; // 存储线程池中的线程
    private final LinkedList<Runnable> taskQueue; // 任务队列
    private final int maxTasks; // 最大任务队列长度
    private final RejectPolicy rejectPolicy; // 拒绝策略
    private volatile boolean isShutdown = false; // 用于标记线程池是否关闭

    // 定义拒绝策略接口
    public interface RejectPolicy {
        void reject(Runnable task);
    }

    // 构造函数,接受最大线程数和任务队列的最大大小
    public SimpleThreadPoolWithRejectPolicy(int poolSize, int maxTasks, RejectPolicy rejectPolicy) {
        this.taskQueue = new LinkedList<>();
        this.maxTasks = maxTasks;
        this.rejectPolicy = rejectPolicy;
        this.workers = new LinkedList<>();

        for (int i = 0; i < poolSize; i++) {
            Worker worker = new Worker();
            workers.add(worker);
            worker.start();
        }
    }

    // 提交任务
    public synchronized void submit(Runnable task) {
        if (isShutdown) {
            throw new IllegalStateException("Thread pool is already shut down");
        }
        // 如果任务队列已满,则执行拒绝策略
        if (taskQueue.size() >= maxTasks) {
            rejectPolicy.reject(task);
        } else {
            taskQueue.add(task);
            notify();  // 唤醒工作线程
        }
    }

    // 关闭线程池
    public synchronized void shutdown() {
        isShutdown = true;
        for (Worker worker : workers) {
            worker.interrupt();  // 中断所有工作线程
        }
    }

    private class Worker extends Thread {
        public void run() {
            while (true) {
                Runnable task;
                synchronized (SimpleThreadPoolWithRejectPolicy.this) {
                    while (taskQueue.isEmpty() && !isShutdown) {
                        try {
                            SimpleThreadPoolWithRejectPolicy.this.wait();  // 等待新任务
                        } catch (InterruptedException e) {
                            return;  // 线程被中断,退出
                        }
                    }
                    task = taskQueue.poll();  // 从任务队列中取任务
                }

                if (task != null) {
                    task.run();  // 执行任务
                }

                if (isShutdown && taskQueue.isEmpty()) {
                    return;  // 线程池关闭且无任务时退出
                }
            }
        }
    }
}
代码中的主要改进
  1. RejectPolicy 接口:定义了一个拒绝策略接口,当任务队列满时,通过此接口处理新提交的任务。
  2. submit() 方法:提交任务时检查任务队列是否已满,若满则调用指定的拒绝策略处理新任务。
三种常见拒绝策略实现
public class RejectPolicies {
    // 抛出异常的拒绝策略
    public static final SimpleThreadPoolWithRejectPolicy.RejectPolicy ABORT_POLICY = task -> {
        throw new RejectedExecutionException("Task " + task + " rejected due to full queue.");
    };

    // 直接丢弃任务的拒绝策略
    public static final SimpleThreadPoolWithRejectPolicy.RejectPolicy DISCARD_POLICY = task -> {
        System.out.println("Task " + task + " is discarded.");
    };

    // 调用者执行任务的拒绝策略
    public static final SimpleThreadPoolWithRejectPolicy.RejectPolicy CALLER_RUNS_POLICY = task -> {
        System.out.println("Task " + task + " is running in caller thread.");
        task.run();
    };
}
使用示例

我们将创建一个最大任务队列长度为2的线程池,并使用不同的拒绝策略来演示效果。

public class CustomThreadPoolWithRejectPolicyDemo {
    public static void main(String[] args) {
        // 使用丢弃策略,最大线程数为2,任务队列最多允许2个任务
        SimpleThreadPoolWithRejectPolicy pool = new SimpleThreadPoolWithRejectPolicy(2, 2, RejectPolicies.DISCARD_POLICY);

        // 提交5个任务,超过队列长度的任务将被丢弃
        for (int i = 1; i <= 5; i++) {
            final int taskId = i;
            pool.submit(() -> {
                System.out.println("Task " + taskId + " is running by thread " + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);  // 模拟任务耗时
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                System.out.println("Task " + taskId + " completed.");
            });
        }

        // 关闭线程池
        pool.shutdown();
    }
}
运行结果示例
Task 1 is running by thread Thread-0
Task 2 is running by thread Thread-1
Task java.util.concurrent.FutureTask@1540e19d is discarded.
Task java.util.concurrent.FutureTask@677327b6 is discarded.
Task java.util.concurrent.FutureTask@14ae5a5 is discarded.
Task 1 completed.
Task 2 completed.

解释:最大任务队列长度为2,当提交第3个任务时,任务队列已满,采用丢弃策略处理,后续任务被丢弃。


其他拒绝策略的效果
  1. Abort Policy

    • 当任务队列已满时抛出 RejectedExecutionException 异常。
    • 适用于不允许任务丢失的场景,通过异常处理机制提醒开发者队列已满。
  2. Caller Runs Policy

    • 直接由调用者线程执行被拒绝的任务,避免任务丢失,但可能拖慢主线程执行。
    • 适用于轻量任务且不能丢弃任务的场景。

线程池中的拒绝策略使用场景
  1. 高并发任务处理:在某些高并发场景下,系统可能无法处理所有任务,线程池中的拒绝策略可以帮助避免系统崩溃。例如:

    • 金融交易系统:不能丢弃关键任务,必须抛出异常警告并进行人工干预。
    • 日志系统:可以丢弃部分非关键的日志任务,防止队列溢出导致系统崩溃。
  2. 限流控制:在请求暴增时,线程池通过拒绝策略来限制同时执行的任务数量,避免资源耗尽或服务器过载。

  3. 调用者线程执行:适用于临时高峰压力,采用调用者线程执行任务,能分摊任务压力,避免任务积压。


总结

通过添加拒绝策略,线程池能够有效处理任务过载情况。不同的拒绝策略适用于不同的业务场景,开发者可以根据需求选择合适的策略。

;