2026/4/5 3:03:12
网站建设
项目流程
化工网站模板,做音乐网站代码,wordpress 5.2更新了什么意思,专业的集团网站建设哪家面试官问#xff1a;线程池拒绝策略怎么选#xff0c;才不会丢任务#xff1f;本文将从原理、策略选型到高阶方案#xff0c;助你从容应对。面试官问#xff1a;线程池拒绝策略怎么选#xff0c;才不会丢任务#xff1f;
当面试官追问“线程池满了如何处理#xff1f;…面试官问 线程池拒绝策略怎么选才不会丢任务本文将从原理、策略选型到高阶方案助你从容应对。面试官问线程池拒绝策略怎么选才不会丢任务当面试官追问“线程池满了如何处理哪种策略能不丢任务”时仅回答“用CallerRunsPolicy”往往不够。面试官真正考察的是结合业务场景选型和规避任务丢失风险的能力。本文将从原理、策略选型到高阶方案助你从容应对。一、拒绝策略触发条件剖析拒绝策略生效的本质是任务提交速率超出线程池处理能力需同时满足核心线程满载所有corePoolSize线程均处于忙碌状态。队列饱和任务队列如LinkedBlockingQueue容量耗尽。线程数达上限线程总数已达maximumPoolSize无法创建新线程。场景示例电商秒杀场景线程池配置corePoolSize5,maximumPoolSize10, 队列容量capacity20。若瞬时涌入 50 个下单任务5 个核心线程处理任务。20 个任务进入队列。创建 5 个非核心线程处理新任务。剩余 10 个任务触发拒绝策略。策略选择不当轻则任务丢失下单失败重则系统崩溃。二、JDK 原生拒绝策略详解JDK 提供 4 种策略实现RejectedExecutionHandler其任务保障性各异1. AbortPolicy (默认策略)抛异常任务必丢逻辑直接抛出RejectedExecutionException任务不执行。ThreadPoolExecutor executor new ThreadPoolExecutor( 5, 10, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue(20), new ThreadPoolExecutor.AbortPolicy() );适用需立即感知任务丢失的场景如金融转账。任务丢失即业务异常。风险未捕获异常导致提交线程崩溃捕获不处理仍丢任务。2. DiscardPolicy静默丢弃隐患最大逻辑直接丢弃新任务无任何通知。代码ThreadPoolExecutor executor new ThreadPoolExecutor( 5, 10, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue(20), new ThreadPoolExecutor.DiscardPolicy() );适用几乎无推荐场景仅适用于日志记录等非核心且可容忍丢失的任务。风险任务“凭空消失”排查困难。3. DiscardOldestPolicy弃旧保新逻辑丢弃队列头部最老任务尝试将新任务入队。代码ThreadPoolExecutor executor new ThreadPoolExecutor( 5, 10, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue(20), new ThreadPoolExecutor.DiscardOldestPolicy() );适用新任务优先级高于旧任务的场景如实时数据统计。风险丢弃的是已入队的任务若为关键业务如订单创建将导致异常。4. CallerRunsPolicy提交线程执行不丢任务逻辑由提交任务的线程如 Tomcat 工作线程直接执行被提任务。代码ThreadPoolExecutor executor new ThreadPoolExecutor( 5, 10, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue(20), new ThreadPoolExecutor.CallerRunsPolicy() );适用核心且不容丢失的任务秒杀下单、用户注册。优势零丢失提交线程不崩溃任务必执行。天然限流提交线程忙于执行任务减缓新任务提交速度。风险若提交线程是关键路径如主线程执行任务会阻塞其本职工作如处理其他请求。三、高阶方案自定义策略解决痛点原生策略存在丢任务或阻塞风险实践中常采用自定义策略1. 方案一MQ 异步重试绝对保任务逻辑拒绝时将任务序列化后发送至消息队列RabbitMQ/RocketMQ/Kafka。消费者异步拉取并重试提交直至成功或记录失败。代码示例// 自定义拒绝策略MQ 重试 RejectedExecutionHandler mqRejectionHandler (Runnable runnable, Executor executor) - { try { String taskJson JSON.toJSONString(runnable); // 序列化任务 rabbitTemplate.convertAndSend(「thread-pool-retry-queue」, taskJson); log.info(「任务入 MQ 重试{}」, taskJson); } catch (Exception e) { // MQ 失败降级CallerRunsPolicy new ThreadPoolExecutor.CallerRunsPolicy().rejectedExecution(runnable, executor); log.error(「任务入 MQ 失败降级处理」, e); } }; // 配置线程池 ThreadPoolExecutor executor new ThreadPoolExecutor( 5, 10, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue(20), mqRejectionHandler // 使用自定义策略 ); // MQ 消费者示例 RabbitListener(queues 「thread-pool-retry-queue」) public void handleRetryTask(String taskJson) { Runnable task JSON.parseObject(taskJson, Runnable.class); for (int i 0; i 3; i) { // 重试 3 次 if (executor.getQueue().remainingCapacity() 0) { executor.submit(task); log.info(「任务重试成功{}」, taskJson); return; } try { TimeUnit.SECONDS.sleep(1); // 间隔 1 秒 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } log.error(「任务重试失败记录到 DB{}」, taskJson); // 记录失败 dbService.saveFailedTask(taskJson); }适用绝对不容丢失的核心业务支付、转账、库存扣减。优势零丢失风险不阻塞提交线程大厂核心业务首选。2. 方案二动态扩容减少拒绝触发逻辑拒绝前尝试动态扩容队列或最大线程数在预设上限内。扩容失败则降级如CallerRunsPolicy。代码示例可扩容队列class ResizableBlockingQueueE extends LinkedBlockingQueueE { private int maxCapacity; // 最大扩容上限 public ResizableBlockingQueue(int initialCapacity, int maxCapacity) { super(initialCapacity); this.maxCapacity maxCapacity; } public boolean expandCapacity(int newCapacity) { if (newCapacity maxCapacity) return false; // ... 实现扩容逻辑 (需考虑线程安全) return true; } Override public boolean offer(E e) { if (super.remainingCapacity() 0) { // 队列满 int newCapacity (int) (size() * 1.2); // 尝试扩容 20% if (expandCapacity(newCapacity)) { log.info(「队列扩容至{}」, newCapacity); } } return super.offer(e); } } // 自定义拒绝策略先扩容后降级 RejectedExecutionHandler expandRejectionHandler (runnable, executor) - { ThreadPoolExecutor pool (ThreadPoolExecutor) executor; ResizableBlockingQueue? queue (ResizableBlockingQueue?) pool.getQueue(); // 1. 尝试扩容队列 if (queue.expandCapacity((int) (queue.size() * 1.2))) { pool.submit(runnable); return; } // 2. 尝试扩容最大线程数 (上限假设为 20) if (pool.getMaximumPoolSize() 20) { pool.setMaximumPoolSize(pool.getMaximumPoolSize() 2); pool.submit(runnable); return; } // 3. 扩容失败降级 CallerRunsPolicy new ThreadPoolExecutor.CallerRunsPolicy().rejectedExecution(runnable, executor); }; // 配置线程池 ThreadPoolExecutor executor new ThreadPoolExecutor( 5, 10, 60L, TimeUnit.SECONDS, new ResizableBlockingQueue(20, 100), // 初始容量 20最大扩容至 100 expandRejectionHandler // 使用自定义策略 );适用流量波动较大的场景如大促预热到峰值过渡期。优势减少拒绝触发频率平衡性能与稳定性。四、面试高频考点与应对CallerRunsPolicy阻塞提交线程如何规避判断提交线程性质如是否为 Tomcat 工作线程是则改用 MQ 策略。为任务执行添加超时控制Future.get(timeout)避免长期阻塞。MQ 重试如何避免任务重复执行幂等设计是关键任务携带唯一标识如订单 ID。执行前检查标识状态查 DB/Redis已执行则跳过。示例下单任务用订单 ID 查 Redis 键order:123:executed。除拒绝策略外如何避免丢任务参数调优corePoolSize 峰值 QPS / 单线程 QPSqueueCapacity 峰值时长 * 单线程 QPS。预热核心线程executor.prestartAllCoreThreads()。提交前检查若executor.getQueue().remainingCapacity() 阈值提前返回“系统繁忙”。五、总结选型与避坑指南选型核心业务保任务 →MQ 重试策略。非核心实时场景保新 →DiscardOldestPolicy。简单场景不阻塞核心线程 →CallerRunsPolicy。避免使用AbortPolicy/DiscardPolicy。避坑CallerRunsPolicy→ 警惕阻塞核心线程。MQ 重试 → 必须实现幂等。动态扩容 → 设定资源上限。核心原则拒绝策略是兜底优化参数核心线程数、队列容量和源头控流提交前检查才是根本。