The CompletableFuture get method has been blocking or throwing TimeoutException

Problem Description

The recently launched service suddenly throws a large number of TimeoutExceptions. After querying, it is found that CompletableFuture is used, and future.get(5, TimeUnit.SECONDS);TimeoutException exceptions are thrown during execution , which results in slow interface response and affects other system calls.

problem analysis

First of all, we know that the value of the get() method of CompletableFuture will block the main thread, and will not cancel the blocking until the child thread completes the task and returns the result. If the child thread does not return to the interface, the main thread will always be blocked, so we generally do not recommend using the get() method of CompletableFuture directly, but using the future.get(5, TimeUnit.SECONDS);method to specify the timeout period.

But when our thread pool rejection policy uses DiscardPolicy or DiscardOldestPolicy, and the thread pool is saturated, we will directly discard the task without throwing any exceptions. Calling the get method at this time is that the main thread will wait for the child thread to return the result until the timeout throws a TimeoutException.

Let's look at the following piece of code:

@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);
        }
    }
}
  •  

We can see that the asynchronous thread in the first step is a very time-consuming thread, and the two CompletableFutures in the second step are a very fast asynchronous operation. According to reason, future1.get(1, TimeUnit.SECONDS)this step should not report TimeOut. But we found that our thread pool rejection strategy uses DiscardPolicy, when the thread pool is full, tasks will be discarded without terminating the main thread. When the get method is executed at this time, the main thread will wait until it times out. So the interface response speed slows down.

solution

  1. When using CompletableFuture, the thread pool rejection strategy is best to use AbortPolicy. Interrupt the main thread directly to achieve the effect of rapid failure.
  2. Time-consuming asynchronous threads and CompletableFuture threads are separated by thread pool, so that time-consuming operations do not affect the execution of the main thread

to sum up

  1. When using CompletableFuture, the thread pool rejection strategy is best to use AbortPolicy. If the thread pool is full, throw an exception directly to interrupt the main thread to achieve the effect of rapid failure
  2. Time-consuming asynchronous threads and CompletableFuture threads are separated by thread pool, so that time-consuming operations do not affect the execution of the main thread
  3. It is not recommended to use the get() method of CompletableFuture directly, but use the future.get(5, TimeUnit.SECONDS);method to specify the timeout

Guess you like

Origin blog.csdn.net/My_Way666/article/details/112563922