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 小时前
RK3528 V4L2 框架 UVC 接口自动获取 H.264 / MJPEG / YUYV 格式:从内核采集到硬解分发
java·网络·实时音视频·视频编解码·h.264
Bazingga1 小时前
一次推理的完整旅程:ReAct推理深度解析
后端
171320330计算机毕设编程1 小时前
计算机毕业设计 基于YOLO改进算法的车辆特征识别系统设计与实现--lw
java·大数据·python·sql·算法
姜鱼问生1 小时前
Nginx gzip 压缩:一个配置让页面加载快 3 倍
后端·算法·架构
badhope1 小时前
Spring AI Alibaba停更:Java程序员的信任,还能被辜负几次?
java·spring
颜酱1 小时前
一栋商业大厦,讲清 InnoDB 的表空间、段、区和页
后端·mysql
const_qiu1 小时前
第一章 1.1-1.2:基础模型调用与 TypeScript 入门 —— Java 开发者的视角
java·javascript·typescript
码事漫谈1 小时前
1956 亿行数据该怎么存:金仓时序数据库的三个实测
后端
Wang's Blog2 小时前
Java 项目实战: 交友项目 - Dubbo 负载均衡策略与实战验证
java·dubbo·交友
Wx-bishekaifayuan2 小时前
springboot桂味美食分享系统40540-计算机课程设计、毕业设计
spring boot·后端·python·django·课程设计·express·旅游