最近刚刚上线的服务忽然抛出大量的TimeoutException,查询后发现是使用了CompletableFuture,而且在执行future.get(5, TimeUnit.SECONDS);
时抛出了TimeoutException异常,致使接口响应很慢进而影响了其余系统的调用。java
首先咱们知道CompletableFuture的get()方法值会阻塞主线程,直到子线程执行任务完成返回结果才会取消阻塞。若是子线程一直不返回接口那么主线程就会一直阻塞,因此咱们通常不建议直接使用CompletableFuture的get()方法,而是使用future.get(5, TimeUnit.SECONDS);
方法指定超时时间。git
可是当咱们的线程池拒绝策略使用的是DiscardPolicy或者DiscardOldestPolicy,而且线程池饱和了的时候,咱们将会直接丢弃任务,不会抛出任何异常。这个时候再来调用get方法是主线程就会一直等待子线程返回结果,直到超时抛出TimeoutException。github
咱们来看下面一段代码:spring
@RunWith(SpringRunner.class) @SpringBootTest public class CompletableFutureTest { Logger logger = LoggerFactory.getLogger(CompletableFutureTest.class); ThreadPoolTaskExecutor taskExecutor = null; @Before public void before() { taskExecutor = new ThreadPoolTaskExecutor(); // 核心线程数 taskExecutor.setCorePoolSize(1); // 最大线程数 taskExecutor.setMaxPoolSize(1); // 队列最大长度 taskExecutor.setQueueCapacity(2); // 线程池维护线程所容许的空闲时间(单位秒) taskExecutor.setKeepAliveSeconds(60); /* * 线程池对拒绝任务(无限程可用)的处理策略 * ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 * ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,可是不抛出异常。 * ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,而后从新尝试执行任务(重复此过程) * ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务,若是执行器已关闭,则丢弃. */ taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy()); taskExecutor.initialize(); } @Test public void testGet() throws Exception { for (int i = 1; i < 100; i++) { new Thread(() -> { // 第一步很是耗时,会沾满线程池 taskExecutor.execute(() -> { sleep(5000); }); // 第二步不耗时的操做,可是get的时候会报TimeoutException CompletableFuture<Object> future1 = CompletableFuture.supplyAsync(() -> 1, taskExecutor); CompletableFuture<Object> future2 = CompletableFuture.supplyAsync(() -> 2, taskExecutor); try { System.out.println(Thread.currentThread().getName() + "::value1" + future1.get(1, TimeUnit.SECONDS)); System.out.println(Thread.currentThread().getName() + "::value2" + future2.get(1, TimeUnit.SECONDS)); } catch (Exception e) { e.printStackTrace(); } }).start(); } sleep(30000); } /** * @param millis 毫秒 * @Title: sleep * @Description: 线程等待时间 * @author yuhao.wang */ private void sleep(long millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { logger.info("获取分布式锁休眠被中断:", e); } } }
咱们能够看到第一步的异步线程时一个很是耗时的线程,第二步的两个CompletableFuture是一个很是快的异步操做。按照道理来讲future1.get(1, TimeUnit.SECONDS)
这一步是不因该报TimeOut的。可是咱们发现咱们线程池拒绝策略使用的是DiscardPolicy,当线程池满了会直接丢弃任务,而不会终止主线程。这个时候执行get方法的时候,主线线程一直会等待直到超时为止。因此接口响应速度一下就慢了下来。缓存
- 在使用CompletableFuture的时候线程池拒绝策略最好使用AbortPolicy,若是线程池满了直接抛出异常中断主线程,达到快速失败的效果
- 耗时的异步线程和CompletableFuture的线程作线程池隔离,让耗时操做不影响主线程的执行
- 不建议直接使用CompletableFuture的get()方法,而是使用
future.get(5, TimeUnit.SECONDS);
方法指定超时时间
https://github.com/wyh-spring-ecosystem-student/spring-boot-student/tree/releases框架
spring-boot-student-completable-future 工程异步
为监控而生的多级缓存框架 layering-cache这是我开源的一个多级缓存框架的实现,若是有兴趣能够看一下。分布式