Future.get() 在配置RejectedExecutionHandler为ThreadPoolExecutor.DiscardPolicy策略时一直阻塞

本文探讨了Java线程池中使用特定拒绝策略时出现的阻塞问题,并提供了三种解决方案,包括修改拒绝策略、使用带超时的获取方法等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转载自:https://blog.csdn.net/doctor_who2004/article/details/115647638

1、先复现这种情况:

package com.sdcuike.java11;

import java.util.concurrent.*;

import com.google.common.util.concurrent.ThreadFactoryBuilder;

public class Demo {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("rpc-pool-%d").setDaemon(true).build();
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1, 1, TimeUnit.HOURS, new SynchronousQueue<>(), threadFactory, new ThreadPoolExecutor.DiscardPolicy() {
            @Override
            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                System.out.println("rejectedExecution");
     
                super.rejectedExecution(r, executor);
            }
        });
     
        threadPoolExecutor.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    TimeUnit.HOURS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
     
        Future<String> future = threadPoolExecutor.submit(new Callable<String>() {
     
            @Override
            public String call() throws Exception {
                return "done";
            }
        });
     
        String result = future.get();
        System.out.println(result);
        System.out.println("done....");
    }

}

线程池中线程的配置为daemon线程(后台运行),当我们的main线程退出时,后台线程也会退出。会输出

done… 而现实的结果是main线程一直阻塞在future.get()调用:

img

2、why?

我们看下源码:java.util.concurrent.FutureTask#get()

public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);
        return report(s);
    }

FutureTask 内部有几种状态:

private static final int NEW          = 0;
    private static final int COMPLETING   = 1;
    private static final int NORMAL       = 2;
    private static final int EXCEPTIONAL  = 3;
    private static final int CANCELLED    = 4;
    private static final int INTERRUPTING = 5;
    private static final int INTERRUPTED  = 6;

当状态state<=COMPLETING 即COMPLETING和NEW两种状态时,会一直阻塞:awaitDone(false, 0L)。

FutureTask的状态设置是在线程池ThreadPoolExecutor执行过程中所设置的,一般情况下线程池队列不满,即不会执行RejectedExecutionHandler处理不能处理的任务时,状态都会设置为state > COMPLETING的某个状态 ,但线程池执行RejectedExecutionHandler的时候,java内置的拒绝策略有:

CallerRunsPolicy
public static class CallerRunsPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code CallerRunsPolicy}.
         */
        public CallerRunsPolicy() { }

        /**
         * Executes task r in the caller's thread, unless the executor
         * has been shut down, in which case the task is discarded.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }
    }

这种策略会使用调用线程池执行任务的当前线程执行,java.util.concurrent.FutureTask#run() 执行肯定会设置新的完成状态。

AbortPolicy
public static class AbortPolicy implements RejectedExecutionHandler {
        /**
         * Creates an {@code AbortPolicy}.
         */
        public AbortPolicy() { }

        /**
         * Always throws RejectedExecutionException.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         * @throws RejectedExecutionException always
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }

这种策略是线程池没配置的情况下使用的 默认策略,直接抛出异常,线程池根本就不会执行任务。

DiscardOldestPolicy
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code DiscardOldestPolicy} for the given executor.
         */
        public DiscardOldestPolicy() { }

        /**
         * Obtains and ignores the next task that the executor
         * would otherwise execute, if one is immediately available,
         * and then retries execution of task r, unless the executor
         * is shut down, in which case task r is instead discarded.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll();
                e.execute(r);
            }
        }
    }

丢弃任务队列队头的任务,任何重试执行提交的新任务。状态肯定会设置。

DiscardPolicy
public static class DiscardPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code DiscardPolicy}.
         */
        public DiscardPolicy() { }

        /**
         * Does nothing, which has the effect of discarding task r.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        }
    }

这种策略什么都不做,任务的状态自然是初始状态,永远不会更新任务的状态,导致java.util.concurrent.FutureTask#get() 一直阻塞。

所以,最好的情况下是不使用这种策略,或者使用java.util.concurrent.FutureTask#get(long, java.util.concurrent.TimeUnit) 带有超时的方法获取结果,或者重写策略,取消任务: ((FutureTask) r).cancel(true);

 @Override
            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                System.out.println("rejectedExecution");
                ((FutureTask) r).cancel(true)
                super.rejectedExecution(r, executor);
            }

3、解决一直阻塞的问题总结

1、重写DiscardPolicy,取消任务: ((FutureTask) r).cancel(true)。
2、不使用DiscardPolicy,建议还是用线程池默认的AbortPolicy,可以重写,打印日志,再调用super.rejectedExecution(r, executor),抛出默认异常行为。
3、使用java.util.concurrent.FutureTask#get(long, java.util.concurrent.TimeUnit) 带有超时的方法获取结果

<think>嗯,用户想了解future.get()阻塞的原因及排查方法。从系统指令看需要严格遵循数学公式格式,但这个问题不涉及数学内容,重点在并发编程分析。 用户提供的引用材料很有价值:引用[1]详细分析了FutureTask.get()阻塞机制和线程池CPU飙高问题,引用[2]提到线程监控发现全部阻塞FutureTask,引用[3]则涉及线程堆栈分析。这些应该作为技术依据。 用户可能遇到的实际场景是:使用线程池提交任务后调用get()程序卡死,需要知道如何定位和解决。需要从阻塞原理、排查方法、解决方案三个层面展开。首先阻塞原因分析要结合源码:1)任务未完成get()会进入awaitDone()的循环等待;2)任务线程被占用导致新任务堆积;3)死锁场景如主线程get()子任务又提交新任务到同线程池。这点在引用[1]的"解决方案"部分有印证。 排查方法要实用:1)线程堆栈分析如引用[3]的trace追踪;2)监控线程池状态如引用[2]的做法;3)超设置验证。特别要注意用户可能不会用专业工具,需给出jstack等基础命令。 解决方案部分重点强调:1)使用带超get();2)分离阻塞任务和非阻塞任务到不同线程池;3)调整线程池参数。引用[1]提到的"FutureTask.get(timeout)方法"是核心方案。 考虑到用户后续可能深入,准备三个相关问题:性能影响分析(呼应引用[1]的CPU飙高)、CompletableFuture替代方案、生产环境最佳实践。这样覆盖了原理-实践-优化的完整链路。最后检查技术点:所有Java并发API名称需准确(如FutureTask.get),每个方案要有理论依据,避免主观建议。由于是技术问题,保持专业严谨但不用数学公式。</think>FutureTask.get() 阻塞的原因分析及排查方法如下: --- ### **一、阻塞原因分析** 1. **任务未完成的等待机制** `Future.get()` 方法会**同步阻塞**调用线程,直到任务完成: - 若任务仍在执行中,调用线程会进入`WAITING`或`TIMED_WAITING`状态(通过`LockSupport.park()`实现)[^1]。 - **典型场景**:任务执行间过长,或线程池资源耗尽导致任务无法及执行。 2. **线程池资源耗尽** - **核心问题**:当线程池的**工作队列已满**且**所有线程忙碌**,新提交的任务会阻塞在`get()`方法上。 - **连锁反应**:主线程因`get()`阻塞,无法释放资源 → 线程池中的任务因依赖主线程资源也被阻塞 → 形成**死锁**或**资源僵局**[^2]。 3. **任务间循环依赖** - **案例**:任务A需等待任务B的结果(通过`B.get()`),但任务B因线程池满载无法执行 → 两者互相等待导致死锁[^3]。 4. **任务执行异常未被处理** - 若任务抛出未捕获异常,`Future.get()`会抛出`ExecutionException`,但若异常处理逻辑错误(如忽略异常),仍可能导致线程阻塞。 --- ### **二、排查方法** #### **1. 线程堆栈分析** - **步骤**: 1. 执行 `jstack <pid>` 导出线程堆栈。 2. 搜索 `Future.get()` 调用链,定位阻塞线程。 3. 检查阻塞线程状态:`WAITING on condition` 或 `TIMED_WAITING`。 - **关键标识**: ```java "main" #1 prio=5 os_prio=0 tid=0x00007f... nid=0x1e1f waiting on condition [0x00007f...] java.lang.Thread.State: WAITING (parking) at sun.misc.Unsafe.park(Native Method) at java.util.concurrent.FutureTask.awaitDone(FutureTask.java:425) // 阻塞点 at java.util.concurrent.FutureTask.get(FutureTask.java:190) ``` #### **2. 线程池状态监控** - **检查指标**: - `corePoolSize` vs. 活跃线程数 - 工作队列大小:`queue.size()` - 任务堆积量:`queue.remainingCapacity()` - **工具**: - JMX(如VisualVM) - 日志输出线程池状态(示例代码): ```java ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(4); log.info("Active: {}, Queue: {}", executor.getActiveCount(), executor.getQueue().size()); ``` #### **3.机制验证** - **诊断方法**: 使用带超的 `get(long timeout, TimeUnit unit)`: ```java try { future.get(5, TimeUnit.SECONDS); // 超抛出 TimeoutException } catch (TimeoutException e) { log.error("任务执行超,可能存在资源死锁"); } ``` 若频繁超,表明线程池处理能力不足或存在死锁。 --- ### **三、解决方案** 1. **避免长阻塞** - **强制使用超**:所有`get()`调用必须设置超间。 - **异步回调替代**: ```java CompletableFuture.supplyAsync(() -> task()) .thenAccept(result -> handleResult(result)); // 非阻塞回调 ``` 2. **优化线程池配置** - **调整队列策略**: - 使用`SynchronousQueue`(避免任务堆积,但需匹配合理线程数)。 - 设置`RejectedExecutionHandler`(如`CallerRunsPolicy`,避免任务丢失)。 - **动态扩容**: ```java new ThreadPoolExecutor(coreSize, maxSize, 60s, new LinkedBlockingQueue<>()); ``` 3. **解除任务依赖** - **拆分阻塞操作**:将`IO`操作与计算任务分离,避免线程长间占用。 - **隔离线程池**:为阻塞任务和非阻塞任务分配独立线程池。 4. **异常捕获标准化** ```java try { future.get(); } catch (ExecutionException e) { Throwable rootCause = e.getCause(); // 获取任务真实异常 log.error("Task failed", rootCause); } ``` --- ### **典型案例复现** ```java ExecutorService executor = Executors.newFixedThreadPool(1); Future<?> future1 = executor.submit(() -> { Future<?> future2 = executor.submit(() -> System.out.println("Inner Task")); // 内层任务 future2.get(); // 阻塞等待内层任务 → 但线程池无空闲线程! }); future1.get(); // 外层任务阻塞 → 死锁形成 ``` **结果**:线程池唯一线程被`future2.get()`阻塞,导致`future2`无法执行 → 永久死锁[^1]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值