ListenableFuture的使用和原理

Guava线程池介绍 简介 ListenableFuture顾名思义就是可以监听的Future,它是对java原生Future的扩展增强。我们知道Future表示一个异步计算任务,当任务完成时可以得到计算结果。如果我们希望一旦计算完成就拿到结果展示给用户或者做另外的计算,就必须使用另一个线程不断的查询计算状态。这样做,代码复杂,而且效率低下。使用ListenableFuture Guava帮我们检测Future是否完成了,如果完成就自动调用回调函数,这样可以减少并发程序的复杂度。 推............ 阅读详情
AI摘要

本文深入解析ListenableFuture在Google Guava框架中的应用,对比FutureTask,重点介绍了如何使用ListenableFuture进行异步任务处理,包括任务提交、回调函数设置及执行原理。

java中有几种实现异步的方式(FutureTask/ListenableFuture/CompletableFuture)
这篇介绍的是ListenableFuture,相比FutureTask,本质上只是增加了任务的回调函数,这个是google框架里面的一个东西,我用的版本是guava-27.0-jre.jar
首先还是说使用:

public class ListenableFutureTest {
    public static void main(String[] args) {
        testListenFuture();
    }

    public static void testListenFuture() {
        System.out.println("主线程start");
        ListeningExecutorService pool = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(5));

        Task task1 = new Task();
        task1.args = "task1";
        Task task2 = new Task();
        task2.args = "task2";
        ListenableFuture<String> future = pool.submit(task1);
        ListenableFuture<String> future2 = pool.submit(task2);

        future2.addListener(() -> System.out.println("addListener 不能带返回值"), pool);

        /**
         * FutureCallBack接口可以对每个任务的成功或失败单独做出响应
         */
        FutureCallback<String> futureCallback = new FutureCallback<String>() {
            @Override
            public void onSuccess(String result) {
                System.out.println("Futures.addCallback 能带返回值:" + result);
            }
            @Override
            public void onFailure(Throwable t) {
                System.out.println("出错,业务回滚或补偿");
            }
        };

        //为任务绑定回调接口
        Futures.addCallback(future, futureCallback, pool);
        System.out.println("主线程end");
    }
}

class Task implements Callable<String> {
    String args;
    @Override
    public String call() throws Exception {
        Thread.sleep(1000);
        System.out.println("任务:" + args);
        return "dong";
    }
}

future2.addListener(() -> System.out.println(“addListener 不能带返回值”), pool);
Futures.addCallback(future, futureCallback, pool);
两种方法都可以,一会说一下原理,其实Futures.addCallback里面也是调用addListener
然后从上到下说一下原理:
上一篇直接用的是Thread(futureTask).start()去执行一个任务
这一篇用了java中的线程池-ThreadPoolExecutor,里面主要是使用execute去执行任务的
MoreExecutors.listeningDecorator就是包装了一下ThreadPoolExecutor,目的是为了使用ListenableFuture

private static class ListeningDecorator extends AbstractListeningExecutorService {
        private final ExecutorService delegate;

        ListeningDecorator(ExecutorService delegate) {
            this.delegate = (ExecutorService)Preconditions.checkNotNull(delegate);
        }

        public final void execute(Runnable command) {
            this.delegate.execute(command);
        }
    }

在这里插入图片描述
在这里插入图片描述
这里的delegate就是ThreadPoolExecutor
虽然还重写了execute,不过还是直接调用ThreadPoolExecutor里面的execute
ListeningExecutorService pool=MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(5));
这样一个执行器就被new出来了,现在需要往里面放任务了
ListenableFuture future = pool.submit(task1);看看submit代码

public <T> ListenableFuture<T> submit(Callable<T> task) {
        return (ListenableFuture)super.submit(task);
    }

然后调用父类的submit

public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }

这里会调用重写的newTaskFor

protected final <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
        return TrustedListenableFutureTask.create(callable);
    }
class TrustedListenableFutureTask<V> extends TrustedFuture<V> implements RunnableFuture<V> {
    private volatile InterruptibleTask<?> task;
    
    static <V> TrustedListenableFutureTask<V> create(Callable<V> callable) {
        return new TrustedListenableFutureTask(callable);
    }

    TrustedListenableFutureTask(Callable<V> callable) {
        this.task = new TrustedListenableFutureTask.TrustedFutureInterruptibleTask(callable);
    }

    public void run() {
        InterruptibleTask localTask = this.task;
        if (localTask != null) {
            localTask.run();
        }

        this.task = null;
    }
}    

创建了一个TrustedListenableFutureTask,里面有个task是TrustedFutureInterruptibleAsyncTask
这里重写了Runnable的run方法,调用的就是这个task得run方法(也就是我们真正的任务)

private final class TrustedFutureInterruptibleAsyncTask extends InterruptibleTask<ListenableFuture<V>> {
        private final AsyncCallable<V> callable;

        TrustedFutureInterruptibleAsyncTask(AsyncCallable<V> callable) {
            this.callable = (AsyncCallable)Preconditions.checkNotNull(callable);
        }
    }

在这里插入图片描述
RunnableFuture ftask = newTaskFor(task);
所以这里返回的是一个RunnableFuture,本质其实是TrustedListenableFutureTask(每个任务都有这么一个future)
execute(ftask);
然后会调用execute,让线程池中某个线程去执行

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }

再说一下addListener,其实就是给任务增加一个回调函数
future2.addListener(() -> System.out.println(“addListener 不能带返回值”), pool);
这里调用的是TrustedListenableFutureTask的addListener
实际上调用的是父类AbstractFuture的addListener

public void addListener(Runnable listener, Executor executor) {
    checkNotNull(listener, "Runnable was null.");
    checkNotNull(executor, "Executor was null.");
    if (!isDone()) {
      Listener oldHead = listeners;
      if (oldHead != Listener.TOMBSTONE) {
        Listener newNode = new Listener(listener, executor);
        do {
          newNode.next = oldHead;
          if (ATOMIC_HELPER.casListeners(this, oldHead, newNode)) {
            return;
          }
          oldHead = listeners; // re-read
        } while (oldHead != Listener.TOMBSTONE);
      }
    }
    // If we get here then the Listener TOMBSTONE was set, which means the future is done, call
    // the listener.
    executeListener(listener, executor);
  }

如果这个任务已经完成,执行executeListener

private static void executeListener(Runnable runnable, Executor executor) {
        try {
            executor.execute(runnable);
        } catch (RuntimeException var3) {
            log.log(java.util.logging.Level.SEVERE, "RuntimeException while executing runnable " + runnable + " with executor " + executor, var3);
        }

    }

这里就是调用执行器的execute方法,相当于给线程池扔一个任务,毕竟回调函数也是一个任务

如果这个任务没有完成,那么把回调函数保存起来,用一个链表保存,链表的头结点就是listener,这个listener里面的回调函数在哪执行呢?在任务执行完之后,我文章最后再说这个问题。
ATOMIC_HELPER.casListeners(this, oldHead, newNode)是给listener赋值的原子操作。

private static final class Listener {
    static final Listener TOMBSTONE = new Listener(null, null);
    final Runnable task;
    final Executor executor;

    // writes to next are made visible by subsequent CAS's on the listeners field
    @Nullable Listener next;

    Listener(Runnable task, Executor executor) {
      this.task = task;
      this.executor = executor;
    }
  }

为什么要用链表呢?因为一个任务可以有多个回调函数

再来看看Futures.addCallback(future, futureCallback, pool);

public static <V> void addCallback(
      final ListenableFuture<V> future,
      final FutureCallback<? super V> callback,
      Executor executor) {
    Preconditions.checkNotNull(callback);
    future.addListener(new CallbackListener<V>(future, callback), executor);
  }

底层用CallbackListener封装了一下,还是调用了addListener
为什么要封装一下呢?因为要拿到任务的返回值

private static final class CallbackListener<V> implements Runnable {
    final Future<V> future;
    final FutureCallback<? super V> callback;

    CallbackListener(Future<V> future, FutureCallback<? super V> callback) {
      this.future = future;
      this.callback = callback;
    }

    @Override
    public void run() {
      final V value;
      try {
        value = getDone(future);
      } catch (ExecutionException e) {
        callback.onFailure(e.getCause());
        return;
      } catch (RuntimeException | Error e) {
        callback.onFailure(e);
        return;
      }
      callback.onSuccess(value);
    }
  }

在这个run方法里面就调用getDone等待返回值,如果有异常就调用callback.onFailure,没有异常就调动callback.onSuccess(value);
这两个方法都是你传入的,这样就能接收到任务的返回值了

 public static <V> V getDone(Future<V> future) throws ExecutionException {
    checkState(future.isDone(), "Future was expected to be done: %s", future);
    return getUninterruptibly(future);
  }
  
public static <V> V getUninterruptibly(Future<V> future) throws ExecutionException {
    boolean interrupted = false;
    try {
      while (true) {
        try {
          return future.get();
        } catch (InterruptedException e) {
          interrupted = true;
        }
      }
    } finally {
      if (interrupted) {
        Thread.currentThread().interrupt();
      }
    }
  }

future.get()就是到等待任务执行结束

现在接着说listener中的回调函数在哪执行的

private final class TrustedFutureInterruptibleTask extends InterruptibleTask<V> {
    private final Callable<V> callable;

    @Override
    void afterRanInterruptibly(V result, Throwable error) {
      if (error == null) {
        TrustedListenableFutureTask.this.set(result);
      } else {
        setException(error);
      }
    }
  }

在任务执行完之后,这里调用了set方法

protected boolean set(@Nullable V value) {
    Object valueToSet = value == null ? NULL : value;
    if (ATOMIC_HELPER.casValue(this, null, valueToSet)) {
      complete(this);
      return true;
    }
    return false;
  }

然后又调用了comlete

private static void complete(AbstractFuture<?> future) {
    Listener next = null;
    outer:
    while (true) {
      future.releaseWaiters();
      next = future.clearListeners(next);
      future = null;
      while (next != null) {
        Listener curr = next;
        next = next.next;
        Runnable task = curr.task;
        if (task instanceof SetFuture) {
          SetFuture<?> setFuture = (SetFuture<?>) task;
          future = setFuture.owner;
          if (future.value == setFuture) {
            Object valueToSet = getFutureValue(setFuture.future);
            if (ATOMIC_HELPER.casValue(future, setFuture, valueToSet)) {
              continue outer;
            }
          }
          // other wise the future we were trying to set is already done.
        } else {
          executeListener(task, curr.executor);
        }
      }
      break;
    }
  }

就是在这里执行完所有的回调函数

JAVA的异步(2):guava-ListenableFuture JAVA异步实现(2):guava-ListenableFutureListenableFutureFutureCallbackSettableFuture 在JDK1.8之前,java官方一直没有提供比Future更好的异步工具(Future相关在前一篇博客中有介绍),为此google的guava库中提供了ListenableFuture作为Future的工具,在JDK1.8中增加了CompletableFuture作为对Future的补充,见下一篇博客。 本文的maven依赖如下: <depen 阅读详情

相关推荐

深入解析Guava并发工具:ListenableFuture、LoadingCache与RateLimiter实战

在Java并发编程中,线程安全与高性能是核心挑战。通过理解并发原理,开发者可以构建更健壮的应用。Guava库提供了一系列高级并发工具,如ListenableFuture实现了基于观察者模式的异步回调,解决了原生Future的阻塞问题;LoadingCache则封装了线程安全的缓存机制,支持自动加载与多种淘汰策略,有效防止缓存击穿。这些工具的技术价值在于将复杂的并发控制抽象为简洁易用的API,提升了开发效率与代码可维护性。在实际应用场景中,它们广泛应用于高并发服务、缓存系统与流量控制等关键领域,是Java开发

331

浅析ListenableFuture使用场景及介绍示例

ListenableFuture是对原有Future的增强,可以用于监听Future任务的执行状况,是执行成功还是执行失败,并提供响应的接口用于对不同结果的处理。 适用场景: 1、如果一个主任务开始执行,然后需要执行各个小任务,并且需要等待返回结果,统一返回给前端,此时Future和ListenableFuture作用几乎差不多,都是通过get()方法阻塞等待每个任务执行完毕返回。

石头视角 1万+

一句话ListenableFuture简介

ListenableFuture 并发是一个难问题,但是通过强有力和简单的抽象,可以简化。为了使问题简化,Guava 使用了JDK 的ListenableFuture接口。 这里强烈推荐诸位使用ListenableFuture而不是Future。原因有如下三点: 大多数的Future方法需要ListenableFuture 切换到ListenableFuture很容易 工具方法的提供者不需要...

1586

Spring番外篇-ListenableFuture

其实更好的方式是给future添加一个listener,成功或者异常后回调listener。,因为功能非常强大,性能也很优秀,Doug Lea大神对其的优化无所不用其极。这个线程池定义了两个方法返回ListenableFuture。什么情况下该使用ListenableFuture。就对原生的Future做了增强,提供了。(redis客户端)就大量地使用了。诞生于Spring4.0,晚于。在Spring团队后面创造的。,当时Netty4也晚于。所以Spring创造了。其他情况下都更推荐使用。

zzzj的博客 1470

04目标检测-Two-stage的目标检测算法

前面我们对进行了对比分析,同时上一篇我们介绍了传统算法在目标检测的原理和效果,此篇我们记录了我学习目标检测算法的深度,深入讲述一下深度学习算法在目标检测的原理和效果。原文链接:https://blog.youkuaiyun.com/qq_41946216/article/details/132800752。版权声明:本文为优快云博主「爱吃面的猫」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。One-stage(YOLO和SSD系列):直接回归目标位置。

qq_41946216的博客 3650

泛微E10 eBuilder低代码平台避坑指南:新手最容易犯的5个错误及解决方案

本文针对泛微E10 eBuilder低代码平台的新手开发者,总结了5个最常见错误及解决方案,包括表单联动配置、流程设计权限、动作流时序、数据加工类型转换和权限管理过度配置等问题。通过实战经验和代码示例,帮助开发者避开常见陷阱,提升开发效率。

weixin_30919429的博客 539

Guava Caches和Guava ListenableFuture学习

1.Guava是什么? Guava是Google的一组核心Java库,其中包括新的集合类型(例如多图和多集),不可变的集合,图形库以及用于并发,I / O,哈希,缓存,原语,字符串等的实用程序!它广泛用于Google的大多数Java项目中,也被许多其他公司广泛使用。 它的github地址为: https://github.com/google/guava 本次主要参照官方文档,对项目中用到的guava caches和guava listenableFuture进行了学习. 2.Guava Caches 2

BestQiang的博客 1315

黏菌算法(Slime Mould Algorithm,SMA)

这是一篇关于黏菌算法的总结博客,包含算法思想,算法步骤,求函数最值(Python实现),算法改进等,持续更新ing

weixin_46838605的博客 1万+

ListenableFuture:让你的Java异步操作更“听话“

想象一下,你让朋友帮你去买杯咖啡(这是一个耗时操作)。普通Future的模式:你不停地问朋友:“咖啡买好了吗?” “买好了吗?” 直到朋友告诉你买好了,你才能拿到咖啡。这期间你啥也干不了,只能干等(这就是的阻塞方式)。ListenableFuture的模式:你告诉朋友:“咖啡买好后,直接送到我工位上就行。” 然后你就继续忙自己的事。朋友买好咖啡后,会主动把咖啡给你送过来(这就是回调)。是Google Guava库提供的一个接口,它是对Java标准Future的增强。它最大的亮点就是允许你注册回调函数。

weixin_51889618的博客 1092

AIGC实战:用ComfyUI和Lora微调打造专属原神角色(含完整工作流)

本文详细介绍了如何利用ComfyUI和Lora微调技术打造专属原神风格角色,涵盖从环境配置、数据集准备到工作流构建和Lora训练的全过程。通过模块化节点设计和高级控制技术,创作者可实现高度个性化的AIGC艺术创作,提升二次元角色生成的效率与质量。

weixin_29185199的博客 348

多模态对齐方案

本文中我们介绍了 13 中常见的大型多模态模型(Large Multimodal Models, LMM),包括 BLIP-2,LLaVA、MiniGPT、Qwen-VL 以及 Ferret 等。最全 LMM 模型结构(13种)综述。单层linear投影、两层mlp。

liguandong 2082

多线程编程:ListenableFuture

ListenableFuture

你个无聊小demo的博客 200

Python爬虫实战:用最新技术高效下载摄影网站图片

本文将深入探讨如何利用Python最新技术栈构建一个功能完整的摄影网站图片爬虫。我们将使用异步编程、智能反爬策略和现代化工具,实现高效、可靠的图片下载系统。高性能异步架构:使用asyncio实现高并发下载智能反爬策略:支持多种反爬措施和浏览器模拟多网站适配:可解析多种摄影网站结构数据管理:完整的下载记录和统计功能扩展性强:支持代理、分布式等高级功能用户友好:提供进度显示和详细日志。

2201_76125261的博客 1644

Retrofit Guava Adapter:用 ListenableFuture 改造异步 HTTP 调用

本指南讲解 Retrofit 官方适配器模块 `adapter-guava`([README](https://link.gitcode.com/i/36d1c20fa890810a6cbe861e788dc878))的核心用法:只需在构建 `Retrofit` 实例时注册 `GuavaCallAdapterFactory`,即可让服务接口方法直接返回 Guava 的 `ListenableFut

gitblog_00190的博客 8

OpenClaw 2026 路线图详解:3 大协作能力升级与社区贡献增长 62% 的实证分析

介绍 OpenClaw 2026 如何通过上下文锚定(Context Anchoring)、协作质量定义和责任边界机制,将 AI 编程工具从单点补全升级为嵌入代码生命周期的协作对齐系统,并详解 `.openclaw/config.yaml` 中 `context_anchor` 配置的实操方法与效果

weixin_42545292的博客 382

RK3568平台(camera篇)点亮imx586摄像头

mipi摄像头硬件确认点:1.供电:5V,2.8V,1.2V,1.8V,reset脚(硬拉3.3,上电的时候从低到高),pwron脚外接3.3V。2,时钟:MCLKOUT是24兆时钟。3.4组差分mipi线。mipi的data线的波形为方波。

weixin_49303682的博客 7257

Spring Boot 异步框架的使用

1. 前言随着数据量和调用量的增长,用户对应用的性能要求越来越高。另外,在实际的服务中,还存在着这样的场景:系统在组装数据的时候,对于数据的各个部分的获取实际上是没有前后依赖关系的。这些问题都很容易让我们想到将这些同步调用全都改造为异步调用。不过自己实现起来比较麻烦,还容易出错。好在Spring已经提供了该问题的解决方案,而且使用起来十分方便。2.Spring异步执行框架的使用方法2.1 mave...

weixin_34356555的博客 659

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

CompletableFuture原理与实践

摘自https://tech.meituan.com/2022/05/12/principles-and-practices-of-completablefuture.html。显然,CompletableFuture的实现更为简洁,可读性更好。

weixin_43526254的博客 186

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

Java中的异步执行Future小结

最近碰到的异步处理的操作比较多,异步就是不等想要的结果返回执行接下来的其他操作,等异步结果返回后直接调用已经注册好的处理方法完成后续操作。异步的思想是非常棒的,相比轮询的方式而言,异步的实现方式无疑是高效并且优雅的。本文介绍了包括Future,AIO和有点类似于单机版的Map-Reduce的fork/join框架。 Guava ListenableFuture 使用JDK提供的线程池E...

Wenqi's Blog 3073

搞定异步编程,我有八种实现方式!

发送短信、邮件、异步更新等,这些都是典型的可以通过异步实现的场景。首先我们先看一个常见的用户下单的场景:在同步操作中,我们执行到发送短信的时候,我们必须等待这个方法彻底执行完才能执行赠送积分这个操作,如果赠送积分这个动作执行时间较长,发送短信需要等待,这就是典型的同步场景。实际上,发送短信和赠送积分没有任何的依赖关系,通过异步,我们可以实现赠送积分和发送短信这两个操作能够同时进行,比如:这就是所谓的异步,是不是非常简单,下面就说说异步的几种实现方式吧。/**

IT_1024的博客 574

AbstractQueuedSynchronizer(十三)——应用之Futures

1.CombinedFuture通过allAsList和successfulAsList获得一个ListenableFuture,底层的实现是CombinedFuture。 CombinedFuture内部用ImmutableCollection保存了一份future的list,里面存储添加进来的future。还有一个List保存每个future对应的value。 2.init方法初始化操作,对每一个future都添加一个监听器listener,在监听器中进行setOneV...

xocupid的博客 254

深入浅出Future Pattern

深入浅出Future Pattern 2013年6月24日小武哥发表评论阅读评论 前几天看hdfs QJM的代码,里面看到一个ListenableFuture,说实话对于Java,目前我还只是通过看代码,遇到没见过的再去查的方式,也着实是没有时间和精力再去通篇研读诸如《thinking in Java》这样的大砖块了,现在这样的方式,目前来说应该是够用了。重点还是放在系统和

pi9nc的专栏 1176

Java线程池原理以及如何使用Guava优化接口的响应速度

线程池原理:当主线程调用execute方法或submit方法提交任务到线程池里面的时候,有空闲核心线程就交给核心线程去处理,核心线程用完,先进入阻塞队列,此时任务没有被执行,核心线程如果空闲就会读取阻塞队列的任务去执行。如果阻塞队列满了,主线程继续提交任务,就会判断当前线程池的容量是否达到了最大线程,如果没有达到最大线程,就会创建新的线程去执行阻塞队列的任务或用户提交的任务。如果达到最大线程就会执行拒绝策略。 核心线程的特点:①没有任务的时候,核心线程可以全部创建好 ②没有任务的时候,核心线程不可以回收 线

zhangzhi1979815592的博客 1389

面试官:列举一下实现异步的8种方式

点击上方“Java基基”,选择“设为星标”做积极的人,而不是积极废人!每天14:00更新文章,每天掉亿点点头发...源码精品专栏原创 | Java 2021超神之路,很肝~中文详细注释的开源项目RPC 框架 Dubbo 源码解析网络应用框架 Netty 源码解析消息中间件 RocketMQ 源码解析数据库中间件 Sharding-JDBC 和 MyCAT 源码解析作业调度中间件 Elast...

weixin_44421461的博客 161

java接口限流_RateLimit-使用guava来做接口限流代码示例

本文主要研究的是RateLimit-使用guava来做接口限流的相关内容,具体如下。一、问题描述某天A君突然发现自己的接口请求量突然涨到之前的10倍,没多久该接口几乎不可使用,并引发连锁反应导致整个系统崩溃。如何应对这种情况呢?生活给了我们答案:比如老式电闸都安装了保险丝,一旦有人使用超大功率的设备,保险丝就会烧断以保护各个电器不被强电流给烧坏。同理我们的接口也需要安装上“保险丝”,以防止非预期的...

weixin_39915308的博客 400

我的心血全在这了,这种方式讲@Async原理,你别再不懂Spring了

returnType) { // 异步任务的返回值类型是CompletableFuture if (CompletableFuture.class.isAssignableFrom(returnType)) { return CompletableFuture.supplyAsync(() -> { try { return task.call();

代码界的扛把子 140
上一篇: Future的使用和原理
下一篇: js中想使用mavonEditor(markdown编辑器)
帅东
博客等级 码龄11年 676粉丝 88原创
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值