一. 默认的拒绝策略

ThreadPoolExceutor.AbortPolicy : 丢弃任务并抛出RejectedExecutionException异常。
        ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。         ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务

ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务

一般来说, 默认的这4种拒绝策略, 在一些场景是不适用的, 比如我们不想丢弃任何任务, 那么前3种拒绝策略就不适用了

第4种拒绝策略比较特殊, 当执行该策略后, 会把当前线程交给 提交任务的线程 去执行这个任务, 比如说你是通过 主线程 开启的线程任务, 那么这个拒绝策略就是把 被线程池拒绝执行的任务, 反手交给 主线程执行, 并发量高的场景下, 会阻塞主线程, 所以也不是很适用, 下面使用伪代码自定义拒绝策略

二. 自定义拒绝策略

如果不想丢弃该任务和不想交给主线程来执行该任务, 我们是不是可以把这些被拒绝执行的任务收集起来? 所以可以考虑使用缓存容器, 并且一定要是并发安全的缓存容器, 所以可以使用 juc 包下的 java.util.concurrent.LinkedBlockingQueue, 先介绍下这个类

LinkedBlockingQueue是一个单向链表实现的阻塞队列, 该队列按 FIFO(先进先出)排序元素, 新元素插入到队列的尾部, 并且队列获取操作会获得位于队列头部的元素, 链接队列的吞吐量通常要高于基于数组的队列, 但是在大多数并发应用程序中, 其可预知的性能要低
        此外, LinkedBlockingQueue还是可选容量的(防止过度膨胀), 即可以指定队列的容量, 如果不指定, 默认容量大小等于Integer.MAX_VALUE, 我这里就不指定队列的容量了

LinkedBlockingQueue在实现 "多线程对竞争资源的互斥访问" 时, 对于该类中的 put 和 take  方法操作分别使用了不同的锁, 对于 put 操作, 通过 "插入锁 putLock" 进行同步, 对于取出操作, 通过 "取出锁takeLock" 进行同步

若某线程(代码中模拟是 SchedulerTask)要取出数据时, 队列正好为空, 则该线程会执 notEmpty.await()进行等待, 当其它某个线程(代码中模拟是线程池的拒绝任务)向队列中插入了数据之后, 会调用 notEmpty.signal() 唤醒 "notEmpty上的等待线程”, 此时, (代码中模拟是 SchedulerTask)会被唤醒从而得以继续运行, 此外, (代码中模拟是 SchedulerTask)在执行取操作前, 会获取 takeLock, 在取操作执行完毕再释放 takeLock

若某线程(代码中模拟是线程池的拒绝任务)要插入数据时, 队列已满, 则该线程会它执行   notFull.await() 进行等待, 当其它某个线程(代码中模拟是 SchedulerTask)取出数据之后,会调用notFull.signal() 唤醒 "notFull上的等待线程", 此时, (代码中模拟是线程池的拒绝任务)就会被唤醒从而得以继续运行, 此外, (代码中模拟是线程池的拒绝任务)在执行插入操作前,会获取 putLock, 在插入操作执行完毕才释放 putLock

2.1 首先自定义拒绝策略实现

自定义一个类实现 java.util.concurrent.RejectedExecutionHandler 接口

public class CustomizeRejectionPolicy implements RejectedExecutionHandler {@Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {if (r != null) {// 线程池没来得及执行的任务先放入队列ThreadReader.put(r);}}
}

2.2 实现一个中间类, 用于声明 LinkedBlockingQueue

@Component
public class ThreadReader {private static final Logger logger = LoggerFactory.getLogger(ThreadReader.class);private static final BlockingQueue<Runnable> BLOCKING_QUEUE = new LinkedBlockingQueue<>();@Qualifier("thread-pool")@Autowiredprivate ThreadPoolExecutor threadPoolExecutor;public static void put(Runnable runnable) {BlockingQueue<Runnable> blockingQueue = getBlockingQueue();try {blockingQueue.put(runnable);} catch (InterruptedException e) {// 忽略}}public void take() {BlockingQueue<Runnable> blockingQueue = getBlockingQueue();if (CollectionUtils.isEmpty(blockingQueue)) {return;}try {Runnable runnable = blockingQueue.take();logger.info(LogUtils.format("取出当前线程池没来得及执行的任务, runnable=<{0}>", runnable));threadPoolExecutor.execute(runnable);} catch (InterruptedException e) {// }}public static BlockingQueue<Runnable> getBlockingQueue() {return BLOCKING_QUEUE;}
}

2.3 定义该队列的取出类, 这里使用的是单线程, 不再是主线程

@Component
public class CustomizeScheduler implements ApplicationListener<ContextRefreshedEvent> {@Autowiredprivate ThreadReader threadReader;@Overridepublic void onApplicationEvent(ContextRefreshedEvent event) {ThreadFactory threadFactory =new ThreadFactoryBuilder().setThreadFactory(new NamedThreadFactory("SchedulerTask")).build();ScheduledExecutorService scheduledExecutor =new ScheduledThreadPoolExecutor(1, threadFactory, new CustomizeRejectionPolicy());// 之后每隔1秒执行队列中没有来得及执行的任务scheduledExecutor.scheduleAtFixedRate(() -> threadReader.take(), 1, 1, TimeUnit.SECONDS);}
}

2.4 模拟测试

首先线程池配置如下, 然后使用 jmeter 模拟2000个请求

@Configuration
public class ThreadPoolConfig {@Bean("thread-pool")public static ThreadPoolExecutor threadPoolExecutor() {ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(8, 16,1000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(100),new NamedThreadFactory("common-service"), new CustomizeRejectionPolicy());threadPoolExecutor.allowCoreThreadTimeOut(true);return threadPoolExecutor;}
}

之后查看控制台, 可以看到日志打印情况, 可以发现 单线程 每隔1秒从队列中捞取一个被线程池拒绝执行的任务, 并把该任务重新加入到线程池中, 再执行

 三. 相关设计模式

        这里使用到了多线程的经典设计模式, Producer-Consumer模式, 线程池提交被拒绝执行的任务, 他相当于生产者, 单线程相当于消费者, 处理这些任务, 中间类 ThreadReader 相当于 Channel 通道, 此设计模式详解可以查看博客 : 多线程基础之设计模式Producer-Consumer模式_canxiusi的博客-CSDN博客

为什么不能直接在拒绝策略中引入单线程去执行被拒绝的任务?

这样程序的性能会降低, 线程池只需要把被拒绝的任务往队列中添加, 无需关心单线程怎么调度的, 也无需等待单线程对这些任务的处理, 就可以立即把下一个任务放入到队列中

自定义线程池拒绝策略缓解高并发下线程池压力相关推荐

  1. 【Java 并发编程】线程池机制 ( 线程池阻塞队列 | 线程池拒绝策略 | 使用 ThreadPoolExecutor 自定义线程池参数 )

    文章目录 一.线程池阻塞队列 二.拒绝策略 三.使用 ThreadPoolExecutor 自定义线程池参数 一.线程池阻塞队列 线程池阻塞队列是线程池创建的第 555 个参数 : BlockingQ ...

  2. 【多线程】线程池拒绝策略详解与自定义拒绝策略

    线程池的拒绝策略 ThreadPoolExecutor内部有实现4个拒绝策略,默认为AbortPolicy策略 CallerRunsPolicy:由调用execute方法提交任务的线程来执行这个任务 ...

  3. 线程池拒绝策略 开发中常用什么策略_面试官:说说你知道多少种线程池拒绝策略...

    往期文章 为什么阿里Java规约要求谨慎使用SimpleDateFormathttps://www.toutiao.com/i6696127929048367629/ 为什么我强烈推荐你用枚举来实现单 ...

  4. 面试官:说说你知道多少种线程池拒绝策略

    前言 线程池,相信很多人都有用过,没用过相信的也有学习过.但是,线程池的拒绝策略,相信知道的人会少许多. 四种线程池拒绝策略 当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPool ...

  5. 自定义线程池拒绝策略

    一. 默认的拒绝策略         ThreadPoolExceutor.AbortPolicy : 丢弃任务并抛出RejectedExecutionException异常.         Thr ...

  6. java线程池拒绝策略_Java核心知识 多线程并发 线程池原理(二十三)

    线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后 启动这些任务,如果线程数量超过了最大数量超出数量的线程排队等候,等其它线程执行完毕, 再从队列中取出任务来执行.他 ...

  7. Java线程池--拒绝策略RejectedExecutionHandler

    当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略: 当线程池的任务缓存队列已满并且线程池中的线程数目达到ma ...

  8. java线程池拒绝策略_Java线程池ThreadPoolExecutor的4种拒绝策略

    最近在做大批量数据采集转换工作,基础数据在本地但是需要调用网络资源完成数据转换.多方面原因在保证良好运行情况下,最多开5个线程进行网络资源调用.方案是基础数据在数据库分页,循环遍历每一条数据,创建调用 ...

  9. Java中线程池拒绝策略——代码讲解

    1.在使用ThreadPoolExecutor创建多线程时候,需要出入多个参数,如下: public ThreadPoolExecutor(int corePoolSize,              ...

最新文章

  1. python发送邮件带附件_Python发送邮件(带附件)
  2. /proc/sys/vm/ 内存参数
  3. 杭电1254java实现(双bfs 优先队列)
  4. 《GitHub入门与实践》
  5. POJ - 3764 The xor-longest Path(字典树性质)
  6. mybatis学习(50):嵌套查询
  7. java类加载器分类_Java 类加载器的种类
  8. 浅谈面向对象编程与面向过程编程
  9. 2014大学计算机考试,2014大学计算机基础考试围参考答案.doc
  10. 编程之美之寻找发帖“水王” 的算法问题
  11. 内存中的 html 网页,网页制作使用html-webpack-plugin'入再内存中生成 html 页面插件...
  12. Web.xml in Hello1 project
  13. win10 查看电脑mac 地址和ip
  14. 信息最全--MySQL循环插入测试用户数据--姓名
  15. 互联网项目经理的职业规划
  16. [数字图像处理]模糊算法用于图像增强
  17. 【GitLab】GitLab CI/CD 模型部署自动化超详细介绍
  18. CISSP-AIO-快速提示内容梳理
  19. SpringBoot+ Dubbo + Mybatis + Nacos +Seata整合来实现Dubbo分布式事务
  20. 耳朵(一)Linux简述

热门文章

  1. studio 不能输入中文
  2. 【附代码实现】光流法大全(DeepFlow、DenseFlow、DisFlow、FbFlow、PCAFlow、SimpleFlow、TV_L1)
  3. Office2016零售版(Retail)转批量授权(VOL)
  4. mysql出现2058_解决yog连接mysql出现2058的问题
  5. 使用微软Azure的tts文本转语音服务出现java.lang.UnsatisfiedLinkError
  6. matlab显示变化的数字,如何在MATLAB中一起显示字符串和数字?
  7. 机器学习笔记:局部加权回归 LOESS
  8. Unity----VR摄像机(浅谈)
  9. Mouse without Borders 让键盘和鼠标飞起来
  10. 如何增长U盘的寿命- 小技巧