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 小时前
基于springboot的食品仓库管理系统
java·spring boot·后端
滴滴答答哒1 小时前
ASP.NET Core + FaceRecognitionDotNet 人脸识别实战:DI 单例、特征向量持久化与 1:N 比对
后端·asp.net
步行cgn1 小时前
FormHttpMessageConverter 详解:Spring MVC 中表单数据的读写转换器
java·spring·mvc
龙亘川1 小时前
构建城市公共安全数字底座:亘川智城智慧安防系统全维度解读
java·大数据·开发语言·人工智能·信息可视化
金銀銅鐵1 小时前
[Java] 用 CLI 展示 EnumMap 增删键值对时的内部变化
java·源码·ai编程
小贺儿开发2 小时前
《流光绘忆》Unity 沉浸式光影互动
科技·unity·ai·程序员·工具·互动·光影
秋天的怀念_David2 小时前
第二十二夜 · 数字迷雾
java·程序人生·算法
Raas1002 小时前
AI网关部署方案?从部署到上线全流程
java·开发语言·人工智能·gateway·ai网关·企业级ai网关·企业级ai产品
niucloud-admin3 小时前
NIUCLOUD PHP SAAS上门家政使用文档:城市管理
java·前端·php
霸道流氓气质3 小时前
智能客服组件:分层意图识别、RAG Advisor 与人工转接状态机的 Vue3 + Spring AI 生产级实战
java·人工智能·spring