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

相关推荐
星轨zb几秒前
从通用到专属:文迹(WenJi)引入 RAG 向量库的技术复盘
java·spring·langchain4j
我是一颗柠檬4 分钟前
【Java后端技术亮点】Feed流三级缓存设计,从10秒到100毫秒的优化实战
java·开发语言·后端·缓存
JaguarJack8 分钟前
PHP 应用 security.txt 漏洞披露实践
后端·php
超梦dasgg9 分钟前
Java 正则表达式 完整详解(语法 + 核心类 + 常用方法 + 实战案例)
java·开发语言·正则表达式
码语智行9 分钟前
操作日志注解模块
java·前端·python
方也_arkling10 分钟前
【Java-Day17】API篇-BigInteger和BigDecimal
java·开发语言
程序员三明治10 分钟前
【AI】RAG 数据分块(Chunk)策略与实践
java·人工智能·后端·ai·大模型·llm·rag
星辰_mya12 分钟前
ThreadLocal之微服务链路追踪
java·开发语言·前端
松仔log14 分钟前
Jetpack——DataStore
java·kotlin
咸鱼翻身小阿橙15 分钟前
文件读写 + Qt Model/View + 自定义分页+搜索过滤
java·数据库·qt