任务队列满了怎么办?四种线程池拒绝策略

AgenticCoding·十二月创作之星挑战赛 10w+人浏览 451人参与

  • 《线程池任务提交入口:execute方法的实现哲学与拒绝策略深度解析》

  • 《从execute到队列:揭秘线程池任务提交的全链路流程》

  • 《任务队列满了怎么办?线程池拒绝策略的四种智慧》

  • 《生产者-消费者模式实战:线程池execute方法的设计与优化》


一、execute方法:线程池的生产者门户

在线程池的架构中,execute(Runnable command)方法是整个系统对外的唯一任务提交入口。这个看似简单的方法背后,蕴含了生产者-消费者模式、资源管理、流量控制等多重设计思想。理解execute方法的实现,是掌握线程池工作原理的关键。

1.1 execute方法的基本职责

在最简化的线程池实现中,execute方法的核心逻辑只有一行代码:

 public void execute(Runnable command) {
     if (command == null) {
         throw new NullPointerException("Task cannot be null");
     }
     taskQueue.offer(command);
 }

但现实世界的线程池远不止如此。一个完整的execute方法需要处理以下问题:

  • 参数校验:确保任务非空

  • 线程池状态检查:确保线程池正在运行

  • 核心线程创建:按需创建新的Worker线程

  • 队列管理:处理队列已满的情况

  • 拒绝策略:当系统过载时的应对措施

二、任务队列:缓冲区的双面性

2.1 队列的容量策略

任务队列作为生产者和消费者之间的缓冲区,其容量策略直接影响系统的行为:

无界队列(如LinkedBlockingQueue)
 private final BlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<>();

优点

  • 永远不会拒绝任务(除非内存耗尽)

  • 实现简单,不需要考虑队列满的情况

  • 适合任务到达率波动大的场景

缺点

  • 可能导致内存溢出(OOM)

  • 无法提供背压(back-pressure)机制

  • 任务积压时响应时间不可控

有界队列(如ArrayBlockingQueue)
 private final BlockingQueue<Runnable> taskQueue = new ArrayBlockingQueue<>(1000);

优点

  • 提供流量控制,防止内存耗尽

  • 强制调用者考虑系统容量

  • 更可预测的系统行为

缺点

  • 需要设计拒绝策略

  • 可能丢失任务

  • 配置合适的队列大小需要经验

2.2 队列满的临界状态

当使用有界队列时,execute方法需要处理队列已满的情况:

 public void execute(Runnable command) {
     if (command == null) throw new NullPointerException();
     
     // 尝试将任务放入队列
     boolean offered = taskQueue.offer(command);
     
     if (!offered) {
         // 队列已满,需要处理拒绝
         handleRejection(command);
     }
 }

这里的offer()方法是非阻塞的,它会立即返回一个布尔值表示是否成功。这与put()方法不同,put()是阻塞的,会在队列满时等待。

三、拒绝策略:系统过载时的智慧应对

当任务队列已满且所有线程都在忙碌时,新提交的任务需要被妥善处理。不同的拒绝策略适应不同的业务场景。

3.1 四大经典拒绝策略

1. AbortPolicy(默认策略)
 private static class AbortPolicy implements RejectionHandler {
     public void rejectedExecution(Runnable task, CustomThreadPool executor) {
         throw new RejectedExecutionException("Task " + task + " rejected from " + executor);
     }
 }

特点:直接抛出异常,强制调用者处理 适用场景:需要明确知道系统过载的场景

2. CallerRunsPolicy
 private static class CallerRunsPolicy implements RejectionHandler {
     public void rejectedExecution(Runnable task, CustomThreadPool executor) {
         if (!executor.isShutdown()) {
             task.run(); // 在调用者线程中直接执行
         }
     }
 }

特点:让调用者线程自己执行任务 优点

  • 不会真正拒绝任务

  • 天然的背压机制:调用者线程变慢,自然减少提交频率

  • 保证任务一定被执行

缺点:可能阻塞调用者,影响整个调用链路

3. DiscardPolicy
 private static class DiscardPolicy implements RejectionHandler {
     public void rejectedExecution(Runnable task, CustomThreadPool executor) {
         // 什么都不做,静默丢弃任务
     }
 }

特点:静默丢弃,不通知调用者 适用场景:对任务丢失不敏感的场景(如日志记录)

4. DiscardOldestPolicy
 private static class DiscardOldestPolicy implements RejectionHandler {
     public void rejectedExecution(Runnable task, CustomThreadPool executor) {
         if (!executor.isShutdown()) {
             executor.getQueue().poll(); // 丢弃队首任务
             executor.execute(task);     // 重试提交新任务
         }
     }
 }

特点:丢弃队列中最老的任务,为新任务腾出空间 适用场景:新任务比旧任务更重要的场景(如实时数据处理)

3.2 自定义拒绝策略实践

在实际业务中,我们经常需要定制化的拒绝策略:

public class CustomRejectionHandler implements RejectionHandler {
    private final int maxRetries;
    private final long waitTimeMillis;
    
    public CustomRejectionHandler(int maxRetries, long waitTimeMillis) {
        this.maxRetries = maxRetries;
        this.waitTimeMillis = waitTimeMillis;
    }
    
    @Override
    public void rejectedExecution(Runnable task, CustomThreadPool executor) {
        for (int i = 0; i < maxRetries; i++) {
            try {
                Thread.sleep(waitTimeMillis);
                if (executor.getQueue().offer(task)) {
                    return; // 重试成功
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new RejectedExecutionException("Interrupted during retry", e);
            }
        }
        // 重试多次后仍然失败
        log.warn("Task rejected after {} retries: {}", maxRetries, task);
        // 可选:将任务持久化到数据库或消息队列
        persistTaskForLaterExecution(task);
    }
}

四、execute方法的完整实现

结合线程池状态管理、队列管理和拒绝策略,一个完整的execute方法实现如下:

public class CustomThreadPool {
    private final BlockingQueue<Runnable> workQueue;
    private final Set<Worker> workers = new HashSet<>();
    private volatile boolean isShutdown = false;
    private final RejectionHandler rejectionHandler;
    private final int corePoolSize;
    private final int maxPoolSize;
    
    public void execute(Runnable command) {
        if (command == null) {
            throw new NullPointerException();
        }
        
        // 检查线程池状态
        if (isShutdown) {
            rejectionHandler.rejectedExecution(command, this);
            return;
        }
        
        // 如果工作线程数小于核心线程数,创建新线程
        synchronized (workers) {
            if (workers.size() < corePoolSize) {
                Worker worker = new Worker(command); // 创建并启动新线程
                workers.add(worker);
                worker.start();
                return;
            }
        }
        
        // 尝试将任务放入队列
        if (workQueue.offer(command)) {
            return; // 成功入队
        }
        
        // 队列已满,尝试创建新线程(不超过最大线程数)
        synchronized (workers) {
            if (workers.size() < maxPoolSize) {
                Worker worker = new Worker(command);
                workers.add(worker);
                worker.start();
                return;
            }
        }
        
        // 队列满且线程数已达上限,执行拒绝策略
        rejectionHandler.rejectedExecution(command, this);
    }
}

五、高级特性与优化

5.1 任务包装与监控

在实际应用中,我们通常需要对原始任务进行包装:

public void execute(Runnable command) {
    Runnable wrappedTask = wrapTask(command);
    // ... 后续处理逻辑
}

private Runnable wrapTask(Runnable original) {
    return () -> {
        long startTime = System.currentTimeMillis();
        try {
            original.run();
            recordSuccess(System.currentTimeMillis() - startTime);
        } catch (Throwable t) {
            recordFailure(t, System.currentTimeMillis() - startTime);
            throw t;
        }
    };
}

5.2 优先级任务支持

通过包装任务,我们可以支持优先级:

 public void execute(Runnable command, int priority) {
     PriorityTask priorityTask = new PriorityTask(command, priority);
     // 使用优先队列
     priorityQueue.offer(priorityTask);
 }
 ​
 private static class PriorityTask implements Comparable<PriorityTask> {
     final Runnable task;
     final int priority;
     
     // 比较逻辑:priority值越大,优先级越高
     @Override
     public int compareTo(PriorityTask other) {
         return Integer.compare(other.priority, this.priority);
     }
 }

5.3 流量统计与动态调整

可以在execute方法中添加流量统计:

 private final AtomicInteger submittedTasks = new AtomicInteger();
 private final AtomicInteger rejectedTasks = new AtomicInteger();
 ​
 public void execute(Runnable command) {
     submittedTasks.incrementAndGet();
     // ... 原有逻辑
     
     // 在拒绝策略中
     rejectionHandler.rejectedExecution(command, this);
     rejectedTasks.incrementAndGet();
     
     // 动态调整:如果拒绝率过高,可以动态调整线程数
     double rejectionRate = (double) rejectedTasks.get() / submittedTasks.get();
     if (rejectionRate > 0.1) { // 拒绝率超过10%
         dynamicallyAdjustPoolSize();
     }
 }

六、实战中的注意事项

6.1 避免任务提交死锁

 // 危险:任务内部提交子任务并等待结果
 public void execute(Runnable command) {
     Future<?> future = threadPool.submit(() -> {
         // 子任务
     });
     future.get(); // 如果所有线程都在等待,形成死锁
 }

解决方案

  1. 使用不同的线程池

  2. 使用CompletableFuture的异步回调

  3. 确保线程池有足够的线程处理嵌套任务

6.2 正确处理任务依赖

 // 任务之间有依赖关系
 public void executeWithDependencies(List<Runnable> tasks, DependencyGraph graph) {
     // 根据依赖图调度任务
     for (Runnable task : getExecutableTasks(graph)) {
         execute(task);
     }
 }

6.3 资源清理与优雅关闭

 public void shutdown() {
     isShutdown = true;
     // 中断所有工作线程
     synchronized (workers) {
         for (Worker worker : workers) {
             worker.interrupt();
         }
     }
 }
 ​
 public List<Runnable> shutdownNow() {
     shutdown();
     // 返回队列中未执行的任务
     List<Runnable> remainingTasks = new ArrayList<>();
     workQueue.drainTo(remainingTasks);
     return remainingTasks;
 }

七、总结

execute方法作为线程池的门户,其设计体现了软件工程中的多个重要原则:

  • 单一职责原则:只负责任务提交,将执行逻辑交给Worker线程

  • 开闭原则:通过拒绝策略接口支持扩展

  • 接口隔离原则:提供简单的执行接口,隐藏内部复杂性

从简单的队列提交到完整的拒绝策略体系,execute方法的演进反映了系统从功能实现到健壮性设计的转变。理解这些设计选择背后的原因,能够帮助我们在实际开发中做出更合理的架构决策。

线程池不仅仅是并发工具,更是资源管理、流量控制和系统稳定性的综合体现。而execute方法,正是这一切的起点。

图1:execute方法完整执行流程

图2:四大拒绝策略对比

图3:线程池状态与任务流向

### 线程池任务队列的种类 1. **ArrayBlockingQueue**:这是一个基于数组的有界阻塞队列,在创建时需要指定队列的大小。当队列满时,新任务将被阻塞,直到队列中有空间可用。它按照先进先出(FIFO)的原则对元素进行排序。 ```java import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class ArrayBlockingQueueExample { public static void main(String[] args) { ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(10); ThreadPoolExecutor executor = new ThreadPoolExecutor( 5, 10, 60, TimeUnit.SECONDS, queue); } } ``` 2. **LinkedBlockingQueue**:基于链表的阻塞队列,有界和无界两种形式。无界队列可以不断地添加任务,直到系统资源耗尽;有界队列在创建时需要指定最大容量。它也是按照 FIFO 原则排序元素。 ```java import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class LinkedBlockingQueueExample { public static void main(String[] args) { LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(20); ThreadPoolExecutor executor = new ThreadPoolExecutor( 5, 10, 60, TimeUnit.SECONDS, queue); } } ``` 3. **SynchronousQueue**:一个不存储元素的阻塞队列,每个插入操作必须等待另一个线程的移除操作,反之亦然。因此,它通常用于创建具有直接交付任务线程池,即任务不会在队列中等待,而是直接交给线程执行。 ```java import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class SynchronousQueueExample { public static void main(String[] args) { SynchronousQueue<Runnable> queue = new SynchronousQueue<>(); ThreadPoolExecutor executor = new ThreadPoolExecutor( 5, 10, 60, TimeUnit.SECONDS, queue); } } ``` 4. **PriorityBlockingQueue**:一个支持优先级排序的无界阻塞队列,元素按照优先级顺序被移除。任务需要实现 `Comparable` 接口来定义优先级。 ```java import java.util.concurrent.PriorityBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; class PriorityTask implements Runnable, Comparable<PriorityTask> { private int priority; public PriorityTask(int priority) { this.priority = priority; } @Override public void run() { System.out.println("Task with priority " + priority + " is running."); } @Override public int compareTo(PriorityTask other) { return Integer.compare(this.priority, other.priority); } } public class PriorityBlockingQueueExample { public static void main(String[] args) { PriorityBlockingQueue<Runnable> queue = new PriorityBlockingQueue<>(); ThreadPoolExecutor executor = new ThreadPoolExecutor( 5, 10, 60, TimeUnit.SECONDS, queue); executor.submit(new PriorityTask(2)); executor.submit(new PriorityTask(1)); } } ``` ### 线程池拒绝策略的种类 1. **AbortPolicy**:默认的拒绝策略,当线程池的线程数量达到最大线程数且任务队列已满时,新提交的任务将被拒绝,并抛出 `RejectedExecutionException` 异常。 ```java import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class AbortPolicyExample { public static void main(String[] args) { ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(10); ThreadPoolExecutor executor = new ThreadPoolExecutor( 5, 10, 60, TimeUnit.SECONDS, queue, new ThreadPoolExecutor.AbortPolicy()); } } ``` 2. **CallerRunsPolicy**:当任务拒绝时,会由提交任务的线程来执行该任务。这样可以减缓新任务的提交速度。 ```java import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class CallerRunsPolicyExample { public static void main(String[] args) { ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(10); ThreadPoolExecutor executor = new ThreadPoolExecutor( 5, 10, 60, TimeUnit.SECONDS, queue, new ThreadPoolExecutor.CallerRunsPolicy()); } } ``` 3. **DiscardPolicy**:直接丢弃新提交的任务,不会抛出任何异常。 ```java import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class DiscardPolicyExample { public static void main(String[] args) { ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(10); ThreadPoolExecutor executor = new ThreadPoolExecutor( 5, 10, 60, TimeUnit.SECONDS, queue, new ThreadPoolExecutor.DiscardPolicy()); } } ``` 4. **DiscardOldestPolicy**:丢弃任务队列中最老的任务,然后尝试重新提交新任务。 ```java import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class DiscardOldestPolicyExample { public static void main(String[] args) { ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(10); ThreadPoolExecutor executor = new ThreadPoolExecutor( 5, 10, 60, TimeUnit.SECONDS, queue, new ThreadPoolExecutor.DiscardOldestPolicy()); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值