FutureTask源码解析

背景

最近项目因为有个同事错误的使用了FutureTask,导致线程池没有真的进行异步操作,使得线上接口报超时,差点产生线上事故

首先看一下同事的代码

java 复制代码
    ...业务代码,支付完成后执行操作  
	try {
            Future<?> future = asyncTaskExecutor.submit(() -> this.afterPaySuccess(oilOrderDO.getOrderNo()));
   	// 打印线程池异常
            future.get();
        } catch (Exception e) {
            LogUtil.error(log, "OrderService.updateOrder.asyncTaskExecutor >> 线程池异常 >> param = {}", param);

        }

目的想必大家都看的出来,就是支付成功后,执行异步操作,比如推送,播报,状态更新等。线程池用过一段时间的朋友应该会发现,他这里拿到了Future,同时调用了Future.get,其实这样子是已经起不到异步的一个效果了,因为Future.get会阻塞线程,直到线程完成后,才会结束,这就会导致,用户支付后,无法直接返回结果,恰巧里面又有一个比较慢的方法,就导致了用户下单后,接口提示超时

源码解析

显然同事,是对这个方法理解错误了,认为他是返回线程池异常而不会阻塞线程,正所谓要知其所以然,虽然找到了原因,但还是要看一下为什么它可以阻塞线程。

先看一下submit方法,我们使用的是ThreadPoolTaskExecutor,本质是给ThreadPoolExecutor封装了一层

java 复制代码
    @Nullable
    private ThreadPoolExecutor threadPoolExecutor;
	public Future<?> submit(Runnable task) {
        ExecutorService executor = this.getThreadPoolExecutor();

        try {
            return executor.submit(task);
        } catch (RejectedExecutionException var4) {
            throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, var4);
        }
    }

ThreadPoolExecutor.submit实际上是有父类AbstractExecutorService实现,这里就构建了FeatureTask对象

java 复制代码
    public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
		// 生成FeatureTask对象
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }

    protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
        return new FutureTask<T>(runnable, value);
    }

    public FutureTask(Runnable runnable, V result) {
		// 将runnable转成callable
        this.callable = Executors.callable(runnable, result);
		// 设置状态
        this.state = NEW;  
    }

然后就是excute就不细讲了,需要的可以看这篇ThreadPoolExecutor源码解析 - 掘金 (juejin.cn),最终的结果肯定是执行callable里的run方法,然后获取返回值。但是这里都没有阻塞的步骤,所以我们先看future.get()方法

首先要清楚future也是有状态的

java 复制代码
    private volatile int state;
	// 新建 
    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;
java 复制代码
    public V get() throws InterruptedException, ExecutionException {
		// 获取当前状态
        int s = state;
		// 线程是NEW或者COMPLETING时等待
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);
        return report(s);
    }

点开awaitDone方法

java 复制代码
    private int awaitDone(boolean timed, long nanos)
        throws InterruptedException {
        final long deadline = timed ? System.nanoTime() + nanos : 0L;
        WaitNode q = null;
        boolean queued = false;
        for (;;) {
            if (Thread.interrupted()) {
                removeWaiter(q);
                throw new InterruptedException();
            }

            int s = state;
			// 执行完毕则,返回状态
            if (s > COMPLETING) {
                if (q != null)
                    q.thread = null;
                return s;
            }
			// 运行中,则挂起
            else if (s == COMPLETING) // cannot time out yet
                Thread.yield();
			// 为空,则新建等待节点
            else if (q == null)
                q = new WaitNode();
			// 新建的节点,放入等待队列
            else if (!queued)
                queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
                                                     q.next = waiters, q);
            // 如果设置超时时间,则判断时间是否超时,超时返回状态,并移除队列
			else if (timed) {
                nanos = deadline - System.nanoTime();
                if (nanos <= 0L) {
                    removeWaiter(q);
                    return state;
                }
				// 阻塞
                LockSupport.parkNanos(this, nanos);
            }
            else
				// 阻塞
                LockSupport.park(this);
        }
    }
java 复制代码
    private V report(int s) throws ExecutionException {
        Object x = outcome;
		// 正常结束,返回callable结果
        if (s == NORMAL)
            return (V)x;
        if (s >= CANCELLED)
            throw new CancellationException();
        throw new ExecutionException((Throwable)x);
    }

这个awaitDone方法看来就是阻塞线程的地方了,它会不停的监听等待队列中的节点,如果完成了,则返回对应的结果,如果未完成则继续等待

总结

当我们使用线程池时,需要考虑我们是否需要同步拿到线程的返回值,如果不需要则不用拿到future对象,否则会导致线程被阻塞,导致接口耗时增加,如果真的需要,建议带上超时时间,防止后续开发人员开发的时候,没有理解到位,将一些耗时很长的方法也放入其中,导致接口超时

相关推荐
苏三说技术1 小时前
Claude Code从失控到起飞,只用了这些技巧
后端
长栎2 小时前
写 for 循环写了十年,你却从没用过迭代器模式最狠的那一面
后端
LiaCode2 小时前
Redis 在生产项目的使用
前端·后端
用户559822481222 小时前
Docker Compose Down 导致容器数据误删——ext4 日志恢复全记录
后端
LiaCode2 小时前
一天学完 redis 的爽翻版核心知识总结
前端·后端
大刚测试开发实战2 小时前
如何内网穿透访问本地私有化部署的TestHub
前端·后端·github
xiaodaoluanzha3 小时前
迄今為止,最簡單的編程語言 Nolang
前端·后端
Csvn3 小时前
Docker 容器管理入门 — 从镜像到容器编排
后端
用户762352425913 小时前
ShardingJDBC
后端
行者全栈架构师3 小时前
IDEA 中 Maven 项目的 15 个红色报错快速解决方法
java·后端