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对象,否则会导致线程被阻塞,导致接口耗时增加,如果真的需要,建议带上超时时间,防止后续开发人员开发的时候,没有理解到位,将一些耗时很长的方法也放入其中,导致接口超时

相关推荐
码兄科技7 小时前
实战:基于Spring Boot + UniApp的地理信息小程序开发
spring boot·后端·uni-app
腾讯云云开发9 小时前
腾讯云 CloudBase 登上 WAIC:我们为 Agent 重新设计了云的生产线
后端
Jelena157795857929 小时前
电商运营分析数据比价接口实战:多平台价格监控与智能决策系统
java·大数据·数据库
星栈9 小时前
从装一堆工具到看懂 Node 工程化思维:我的项目复盘记录
后端·node.js
65岁退休Coder9 小时前
LangChain v1.3.4 笔记 - 05 Agent 上下文记忆
后端
颜酱9 小时前
04 | 召回前置准备:搭好召回所需的四个数据库
前端·人工智能·后端
JaneConan10 小时前
鸿蒙 韶非 UI 系列:能力调用 startAbilityForResult,跳能力拿回参,鸿蒙能力路由入门
后端·harmonyos
晴空了无痕10 小时前
从 Go 基础到 K8s:一条可落地的 Go 服务端成长路线
开发语言·后端·golang·kubernetes
用户83562907805110 小时前
如何使用 Python 在 Excel 中添加、编辑和删除超链接
后端·python
神明不懂浪漫10 小时前
【第五章】Java中的继承与多态
java·开发语言