首先介绍一下ThreadPoolExecutor构造方法:

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {if (corePoolSize < 0 ||maximumPoolSize <= 0 ||maximumPoolSize < corePoolSize ||keepAliveTime < 0)throw new IllegalArgumentException();if (workQueue == null || threadFactory == null || handler == null)throw new NullPointerException();this.corePoolSize = corePoolSize;this.maximumPoolSize = maximumPoolSize;this.workQueue = workQueue;this.keepAliveTime = unit.toNanos(keepAliveTime);this.threadFactory = threadFactory;this.handler = handler;}

构造方法中的字段含义如下

  • corePoolSize:核心线程数量,当有新任务在execute()方法提交时,会执行以下判断:

    • 如果运行的线程少于 corePoolSize,则创建新线程来处理任务,即使线程池中的其他线程是空闲的;
    • 如果线程池中的线程数量大于等于 corePoolSize 且小于 maximumPoolSize,则只有当workQueue满时才创建新的线程去处理任务;
    • 如果设置的corePoolSize 和 maximumPoolSize相同,则创建的线程池的大小是固定的,这时如果有新任务提交,若workQueue未满,则将请求放入workQueue中,等待有空闲的线程去从workQueue中取任务并处理;
    • 如果运行的线程数量大于等于maximumPoolSize,这时如果workQueue已经满了,则通过handler所指定的策略来处理任务
    • 所以,任务提交时,判断的顺序为 corePoolSize –> workQueue –> maximumPoolSize。
  • maximumPoolSize:最大线程数量
  • workQueue:等待队列,当任务提交时,如果线程池中的线程数量大于等于corePoolSize的时候,把该任务封装成一个Worker对象放入等待队列;
  • keepAliveTime:线程池维护线程所允许的空闲时间。当线程池中的线程数量大于corePoolSize的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了keepAliveTime;
  • threadFactory:它是ThreadFactory类型的变量,用来创建新线程。默认使用Executors.defaultThreadFactory() 来创建线程。使用默认的ThreadFactory来创建线程时,会使新创建的线程具有相同的NORM_PRIORITY优先级并且是非守护线程,同时也设置了线程的名称。
  • handler它是RejectedExecutionHandler类型的变量,表示线程池的饱和策略。如果阻塞队列满了并且没有空闲的线程,这时如果继续提交任务,就需要采取一种策略处理该任务。线程池提供了4种策略:
    • AbortPolicy:直接抛出异常,这是默认策略;
    • CallerRunsPolicy:用调用者所在的线程来执行任务;
    • DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
    • DiscardPolicy:直接丢弃任务;

简单来说,在执行execute()方法时如果状态一直是RUNNING时,的执行过程如下:

  1. 如果workerCount < corePoolSize,则创建并启动一个线程来执行新提交的任务;
  2. 如果workerCount >= corePoolSize,且线程池内的阻塞队列未满,则将任务添加到该阻塞队列中;
  3. 如果workerCount >= corePoolSize && workerCount < maximumPoolSize,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务;
  4. 如果workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满, 则根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常。

实验:拒绝策略CallerRunsPolicy

测试当拒绝策略是CallerRunsPolicy时,用调用者所在的线程来执行任务,是什么现象。

实验环境

  • jdk 1.8
  • postman模拟并发
  • mysql5.7
  • intellj
  • springboot 2.1.4.RELEASE

实验代码

本实验代码,在https://github.com/vincentduan/mavenProject 下的threadManagement目录下。

实验步骤

1.  首先配置maven pom.xml文件内容,关键内容如下:

<dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>2.1.4.RELEASE</version><scope>import</scope><type>pom</type></dependency></dependencies></dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.6</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.6</version></dependency>

2. 配置数据库连接池:

@SpringBootConfiguration
public class DBConfiguration {@Autowiredprivate Environment environment;@Beanpublic DataSource createDataSource() {DruidDataSource druidDataSource = new DruidDataSource();druidDataSource.setUrl(environment.getProperty("spring.datasource.url"));druidDataSource.setUsername(environment.getProperty("spring.datasource.username"));druidDataSource.setPassword(environment.getProperty("spring.datasource.password"));druidDataSource.setDriverClassName(environment.getProperty("spring.datasource.driver-class-name"));return druidDataSource;}}

3. 配置线程池:

@Configuration
@EnableAsync
@Slf4j
public class ExecutorConfig {@AutowiredVisiableThreadPoolTaskExecutor visiableThreadPoolTaskExecutor;@Beanpublic Executor asyncServiceExecutor() {log.info("start asyncServiceExecutor");ThreadPoolTaskExecutor executor = visiableThreadPoolTaskExecutor;//配置核心线程数executor.setCorePoolSize(5);//配置最大线程数executor.setMaxPoolSize(5);//配置队列大小executor.setQueueCapacity(5);//配置线程池中的线程的名称前缀executor.setThreadNamePrefix("async-service-");// rejection-policy:当pool已经达到max size的时候,如何处理新任务// CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());//执行初始化executor.initialize();return executor;}
}

VisiableThreadPoolTaskExecutor类作为一个bean,并且继承了ThreadPoolTaskExecutor;

4. 接下来创建一个service,为了模拟并发,我们将方法执行sleep了30秒。如下:

@Service
@Slf4j
public class UserThreadServiceImpl implements UserThreadService {@AutowiredUserThreadDao userThreadDao;@Override@Async("asyncServiceExecutor")public void serviceTest(String username) {log.info("开启执行一个Service, 这个Service执行时间为30s, threadId:{}",Thread.currentThread().getId());userThreadDao.add(username, Integer.parseInt(Thread.currentThread().getId() +""), "started");try {Thread.sleep(30000);} catch (InterruptedException e) {e.printStackTrace();}log.info("执行完成一个Service, threadId:{}",Thread.currentThread().getId());userThreadDao.update(username, Integer.parseInt(Thread.currentThread().getId() +""), "ended");}@Overridepublic void update(String username, int threadId, String status) {userThreadDao.update(username, threadId, status);}
}

5. 其中的dao很简单,就是往数据库插入数据和更新数据。

6. 创建web Controller:

@RestController
@RequestMapping("/serviceTest")
public class TestController {@AutowiredUserThreadService userThreadService;@Autowiredprivate VisiableThreadPoolTaskExecutor visiableThreadPoolTaskExecutor;@GetMapping("test/{username}")public Object test(@PathVariable("username") String username) {userThreadService.serviceTest(username);JSONObject jsonObject = new JSONObject();ThreadPoolExecutor threadPoolExecutor = visiableThreadPoolTaskExecutor.getThreadPoolExecutor();jsonObject.put("ThreadNamePrefix", visiableThreadPoolTaskExecutor.getThreadNamePrefix());jsonObject.put("TaskCount", threadPoolExecutor.getTaskCount());jsonObject.put("completedTaskCount", threadPoolExecutor.getCompletedTaskCount());jsonObject.put("activeCount", threadPoolExecutor.getActiveCount());jsonObject.put("queueSize", threadPoolExecutor.getQueue().size());return jsonObject;}}

执行测试

因为CorePoolSize数量是5,MaxPoolSize也是5,因此线程池的大小是固定的。而QueueCapacity数量也是5。因此当请求前5次时,返回响应:

当前线程数达到CorePoolSize时,新来的请求就会进入到workQueue中,如下所示:

而QueueCapacity的数量也是5,因此达到QueueCapacity的最大限制后,同时也达到了MaxPoolSize的最大限制,将会根据拒绝策略来处理该任务,这里的策略是CallerRunsPolicy时,用调用者所在的线程来执行任务,表现为当前页面被阻塞住了,直到当前调用者所在的线程执行完毕。如下所示:

从控制台的输出也能看出CallerRunsPolicy策略执行线程是调用者线程:

2019-08-19 21:06:50.255  INFO 1302 --- [async-service-4] c.a.i.s.impl.UserThreadServiceImpl       : 开启执行一个Service, 这个Service执行时间为30s, threadId:51
2019-08-19 21:06:50.271  INFO 1302 --- [async-service-4] cn.ac.iie.dao.UserThreadDao              : threadId:51, jdbcTemplate:org.springframework.jdbc.core.JdbcTemplate@5d83694c
2019-08-19 21:06:50.751  INFO 1302 --- [async-service-5] c.a.i.s.impl.UserThreadServiceImpl       : 开启执行一个Service, 这个Service执行时间为30s, threadId:52
2019-08-19 21:06:50.771  INFO 1302 --- [async-service-5] cn.ac.iie.dao.UserThreadDao              : threadId:52, jdbcTemplate:org.springframework.jdbc.core.JdbcTemplate@5d83694c
2019-08-19 21:06:55.028  INFO 1302 --- [nio-8080-exec-1] c.a.i.s.impl.UserThreadServiceImpl       : 开启执行一个Service, 这个Service执行时间为30s, threadId:24
2019-08-19 21:06:55.036  INFO 1302 --- [nio-8080-exec-1] cn.ac.iie.dao.UserThreadDao              : threadId:24, jdbcTemplate:org.springframework.jdbc.core.JdbcTemplate@5d83694c

其中[nio-8080-exec-1]表示就是调用者的线程。而其他线程都是[async-service-]

ThreadPoolExecutor里面4种拒绝策略--CallerRunsPolicy相关推荐

  1. ThreadPoolExecutor 的八种拒绝策略 | 含番外!

    点击蓝色"程序猿DD"关注我 回复"资源"获取独家整理的学习资料! 作者 | KL 来源 | http://rrd.me/en3Wp 前言 谈到java的线程池 ...

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

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

  3. ThreadPoolExecutor 八种拒绝策略,对的,不是4种

    转载自  ThreadPoolExecutor 八种拒绝策略,对的,不是4种 前言 谈到 Java 的线程池最熟悉的莫过于 ExecutorService 接口了,jdk1.5 新增的 java.ut ...

  4. 击穿线程池面试题:3大方法,7大参数,4种拒绝策略

    前言:多线程知识是Java面试中必考的点.本文详细介绍--线程池.在实际开发过程里,很多IT从业者使用率不高,也只是了解个理论知识,和背诵各种八股文,没有深入理解到脑海里,导致面试完就忘.--码农 = ...

  5. 线程池:4个方法,7个参数,4种拒绝策略

    什么是池? 先讲一个例子,有可能可以帮助你理解,觉得无趣的小伙伴可以直接跳过.相信大多数都知道外包公司,甚至很多小伙伴还在外包公司呆过,其实外包公司我觉得也就可以看作是个"池". ...

  6. 线程池的四种拒绝策略

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

  7. 线程池三大方法,七大参数,四种拒绝策略

    线程和进程: 进程: 一个程序,是执行程序的一次执行过程. 一个进程往往包含若干个线程,线程是cpu调度和执行的单位. Java默认有2个线程:main.GC 池化技术: 01:程序的运行,本质 :占 ...

  8. Java多线程学习七:线程池的 4 种拒绝策略和 6 种常见的线程池

    以便在必要的时候按照我们的策略来拒绝任务,那么拒绝任务的时机是什么呢?线程池会在以下两种情况下会拒绝新提交的任务. 第一种情况是当我们调用 shutdown 等方法关闭线程池后,即便此时可能线程池内部 ...

  9. ThreadPoolExecutor 八种拒绝策略,对的,不是4种!

    点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达今日推荐:2020年7月程序员工资统计,平均14357元,又跌了,扎心个人原创100W+访问量博客:点击前往,查看更多 来源 ...

最新文章

  1. 以赛促学,飞桨助力大学生智能车竞赛升级
  2. 圆柱属于能滚动的物体吗_中班科学活动教案:滚动的物体教案(附教学反思)
  3. 《dinv into python》开始了解python
  4. java初学者指南_企业Java中事务隔离级别的初学者指南
  5. ajax 表格删除,jQuery AJAX删除只捕获第一个表格
  6. 类型转为数字_JavaScript自动数据类型的转换
  7. sysstat linux系统性能分析
  8. 阿里云全新一代FaaS平台F3(VU9P)实例发布
  9. 80后营销人如何为理想插上丰满“羽翼”?
  10. 代码示例_mmap的实现
  11. django常见面试题
  12. 利用vlmcs客户端区分KMS服务器是KMS模拟器还是正版微软KMS服务器
  13. 样条插值(Spline)
  14. linux Fedora安装桌面,在Fedora Linux上安装Elementary OS桌面的方法
  15. python制作工资表_Python实用案例:一秒自动生成工资条。
  16. 【评测】Tecan品牌系列产品
  17. linux 桌面 v2ex,程序员:他人笑我桌面太凌乱,我笑他人看不穿
  18. ORACLE 11g自带DBMS函数包
  19. Android后台监听耳机(线控、蓝牙)按键事件
  20. Redis 的特点及命令大全

热门文章

  1. SqlBulkCopy加了事务真的会变快吗?
  2. Struts DispatchAction
  3. django2.0.6 连接使用redis集群
  4. PHP.ini 中的错误提示
  5. Redis缓存穿透 缓存击穿 缓存雪崩原因及其解决方案
  6. PHP中判断字符串是否全是中文eregi函数或含有中文preg_match函数
  7. pr如何跳到关键帧_教你如何使用Final cut pro X制作拉伸缩放旋转的效果|键盘|final|pro|cut...
  8. 平面设计常用的图像文件格式
  9. eprom是计算机内存吗,EEPROM和EPROM存储器详解
  10. SpringBoot+MyBatis登录案例