最近剛剛上線的服務忽然拋出大量的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這是我開源的一個多級緩存框架的實現,若是有興趣能夠看一下。分佈式