背景

有一个功能,这个功能里需要调用几个不同的RPC请求,一开始不以为然,没觉得什么,所以所有的RPC请求都是串行执行,后来发现部分RPC返回时间比较长导致此功能接口时间耗时较长,于是乎就使用了JDK8新特性CompletableFuture打算将这些不同的RPC请求异步执行,等所有的RPC请求结束后,再返回请求结果。

因为功能比较简单没什么特殊的,所以这里在使用CompletableFuture的时候,并没有自定义线程池,默认那么就是ForkJoinPool。下面看下伪代码:

        CompletableFuture task1 = CompletableFuture.runAsync(()->{            /**             * 这里会调用一个RPC请求,而这个RPC请求处理的过程中会通过SPL机制load指定接口的实现,这个接口所在jar存在于WEB-INFO/lib             */            System.out.println("任务1执行");        });        CompletableFuture task2 = CompletableFuture.runAsync(()->{            System.out.println("任务2执行");        });        CompletableFuture task3 = CompletableFuture.runAsync(()->{            System.out.println("任务3执行");        });        // 等待所以任务执行完成返回        CompletableFuture.allOf(task1,task2,task3).join();        return result;

其实初步上看,这段代码没什么特别的,每个任务都是调用一个RPC请求。初期测试这段代码的时候是通过IDEA启动项目,也就是用的是 SpringBoot 内嵌 Tomcat启动的,这段代码功能正常。然后呢,代码开始commit,merge。

到了第二天之后,同事测试发现这段代码抛出了异常,而且这个功能是主入口,那么就是说大大的阻塞啊,此时我心里心情是这样的

立马上后台看日志,但是却发现这个异常是RPC内部处理时抛出来的,第一反应那就是找上游服务提供方,问他们是不是改接口啦?准备开始甩锅!

然后结果就是没有!!! 于是乎我又跑了下项目,测试了一下接口,没问题!确实没问题!卧槽???还有更奇怪的事情,那就是同时装了好几套环境,其他环境是没问题的,此时就没再去关注,后来发现只有在重启了服务器之后,这个问题就会作为必现问题,着实头疼。

问题定位

到这里只能老老实实去debug RPC调用过程的源码了。也就是代码示例中写的,RPC调用过程中,会使用ServiceLoader去找XX接口对应的实现类,而这个配置是在RPC框架的jar包中,这个jar包那自然肯定是在对应微服务的WEB-INFO/lib里了。

这段源码大概长这样吧:

       ArrayList list = new ArrayList();        ServiceLoader serviceLoader = ServiceLoader.load(xxx interface);        serviceLoader.forEach(xxx->{            list.add(xxx)        });

这步执行完后,如果list是空的,那就会抛个异常,这个异常就是前面所说RPC调用过程中的异常了。

到这里,加载不到,那就要怀疑ClassLoader了,先看下ClassLoader加载范围

  • Bootstrap ClassLoader

%JRE_HOME%lib 下的 rt.jar、resources.jar、charsets.jar 和 class

  • ExtClassLoader

%JRE_HOME%libext 目录下的jar包和class

  • AppClassLoader

当前应用ClassPath指定的路径中的类

  • ParallelWebappClassLoader

这个就属于Tomcat自定义ClassLoader了,可以加载当前应用下WEB-INFO/lib

再看下ServiceLoader的实现:

    public static ServiceLoader load(Classservice) {        ClassLoader cl = Thread.currentThread().getContextClassLoader();        return ServiceLoader.load(service, cl);    }    public static ServiceLoader load(Classservice, ClassLoader loader) { return new ServiceLoader<>(service, loader); } private ServiceLoader(Class svc, ClassLoader cl) { service = Objects.requireNonNull(svc, "Service interface cannot be null"); loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; reload(); }

调用load的时候,先获取当前线程的上下文ClassLoader,然后调用new,进入到ServiceLoader的私有构造方法中,这里重点有一句 loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; ,如果传入的classLoader是null(null就代表是BootStrapClassLoader),就使用ClassLoader.getSystemClassLoader(),其实就是AppClassLoader了。

然后就要确定下执行ServiceLoader.load方法时,最终ServiceLoader的loader到底是啥?

  • 1.Debug 通过Sring Boot 内嵌Tomcat启动的应用

在这种情况下ClassLoader是org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader

  • 2.Debug 通过Tomcat启动的应用

在这种情况下ClassLoader是AppClassLoader,通过Thread.currentThread().getContextClassLoader()获取到的是null

真相已经快要接近,为啥同样的代码,Tomcat应用启动的获取到的线程当前上下文类加载器却是BootStrapClassLoader呢?

问题就在于CompletableFuture.runAsync这里,这里并没有显示指定Executor,所以会使用ForkJoinPool线程池,而ForkJoinPool中的线程不会继承父线程的ClassLoader。enmm,很奇妙,为啥不继承,也不知道。。。

问题印证

下面通过例子来证实下,先从基本的看下,这里主要是看子线程会不会继承父线程的上下文ClassLoader,先自定义一个ClassLoader,更加直观:

class MyClassLoader extends ClassLoader{    }

测试一

    private static void test1(){        MyClassLoader myClassLoader = new MyClassLoader();        Thread.currentThread().setContextClassLoader(myClassLoader);        // 创建一个新线程       new Thread(()->{           System.out.println( Thread.currentThread().getContextClassLoader());       }).start();    }

输出

classloader.MyClassLoader@4ff782ab

测试结论: 通过普通new Thread方法创建子线程,会继承父线程的上下文ClassLoader

*源码分析: 查看new Thread创建线程源码发现有如下代码

        if (security == null || isCCLOverridden(parent.getClass()))            this.contextClassLoader = parent.getContextClassLoader();        else            this.contextClassLoader = parent.contextClassLoader;

所以子线程的上下文ClassLoader会继承父线程的上下文ClassLoader

测试二

Tomcat容器环境下执行下述代码

        MyClassLoader myClassLoader = new MyClassLoader();        Thread.currentThread().setContextClassLoader(myClassLoader);        CompletableFuture task1 = CompletableFuture.runAsync(() -> {            System.out.println(Thread.currentThread().getContextClassLoader());        });

输出

null

但是如果通过main函数执行上述代码,依然是会打印出自定义类加载器

为啥呢?查了一下资料,Tomcat 默认使用SafeForkJoinWorkerThreadFactory作为ForkJoinWorkerThreadFactory,然后看下SafeForkJoinWorkerThreadFactory源码

    private static class SafeForkJoinWorkerThread extends ForkJoinWorkerThread {        protected SafeForkJoinWorkerThread(ForkJoinPool pool) {            super(pool);            this.setContextClassLoader(ForkJoinPool.class.getClassLoader());        }    }

这里发现,ForkJoinPool线程设置的ClassLoader是java.util.concurrent.ForkJoinPool的类加载器,而此类位于rt.jar包下,那它的类加载器自然就是BootStrapClassLoader了

问题解决

解决方式一:

        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();        CompletableFuture task1 = CompletableFuture.runAsync(() -> {            Thread.currentThread().setContextClassLoader(contextClassLoader);        });

那就是在ForkJoinPool线程中再重新设置一下上下文ClassLoader

解决方式二:

        CompletableFuture task1 = CompletableFuture.runAsync(() -> {                 },new MyExecutorService());

那就是不使用CompletableFuture的默认线程池ForkJoinPool,转而使用我们的自定义线程池

作者:我有一只喵喵

链接:https://juejin.cn/post/6909445190642040846

来源:掘金

接口没获取到就被使用_使用CompletableFuture时,那些令人头疼的问题相关推荐

  1. wordpress 外部数据接口_使用接口方式获取WordPress用户信息的方法

    今天WordPress主题站简单介绍一下WordPress系统中用户信息获取方式,今天就讲讲使用接口方式获取WordPress用户信息的方法. 接口文件如下: if ('POST' != $_SERV ...

  2. mysql的ole db 访问接口msdasql的数据源对象_无法从链接服务器 (null) 的 OLE DB 访问接口 MSDASQL 获取列信息...

    无法从链接服务器"(null)"的OLEDB访问接口"MSDASQL"获取列信息.链接服务器"(null)"的OLEDB访问接口" ...

  3. 调用后台接口返回报错前端隐藏提示_腾讯社交联盟广告

    开发者帮助中心 优量汇服务体系升级了,除查阅本页常见问题外,还可以通过以下渠道解决您遇到的问题 1. 实时智能客服 入口:优量汇官网.开发者平台.优量汇服务号 时间:7*24即时问答 服务内容:涵盖 ...

  4. 股市量化交易接口如何获取A股历史数据?

    股市量化交易接口其实也是对散户开放等是比较安全稳定接口,但是其接口通过第三方券商完成交易,主要用做于个人或机构做私募等量化投资数据参考的首选,比如说在股市中进行量化投资时,通过接口策略的定制将股票数据 ...

  5. 计算机usb端口没反应,技术编辑教您电脑usb接口没反应怎么办

    近来,有好多小伙伴反应有电脑USB接口不能使用的情况,鼠标键盘通通没反应.针对电脑usb接口没反应的问题,小编整理了常见的原因以及解决方法,希望能帮助你们解决问题 USB是一个外部总线标准,用于规范电 ...

  6. python excel token_python+excel接口自动化获取token并作为请求参数进行传参操作

    1.登录接口登录后返回对应token封装: import json import requests from util.operation_json import OperationJson from ...

  7. jq动态渲染后获取不到元素高度_浏览器的渲染机制

    面试肯定会问到这个吧~ So:再一次的屡屡浏览器的渲染机制~ 在渲染一开始会先从网络层获取请求文档(HTML.XML)的内容,然后再进行以下基本流程 3.1 解析HTML => DOM树 从HT ...

  8. 沪深股票接口如何获取所有股票代码?

    沪深股票接口如何获取所有股票代码呢?对于这个问题,相信大家也有同样的疑问,毕竟涉及到股票交易,运用自动化交易系统进行查询股票代码数据也是很直接的方法,那么接下来,小编就把沪深股票接口获取股票代码的程序 ...

  9. 东财量化接口怎么获取?

    东财量化接口怎么获取? 第一种方法就是自己懂编程,可以自己做一个出来,但是这样会有很大的问题就是不一定会符合券商平台的,就是不兼容的问题. 第一种方法就是找到相对应的平台,进行一个咨询,入手一个正规的 ...

  10. 计算机usb端口没反应,解决电脑USB接口没反应的小方法

    很多用户在使用电脑的时候,都需要用到移动设备等,但是不少用户在使用的过程中,总是会碰到各种问题.比如将U盘插入电脑的时候,电脑根本没有任何的反映,这是怎么回事呢?电脑USB接口没反应要怎么办?今天U大 ...

最新文章

  1. MFC之进度条CProgressCtrl
  2. 五轮阿里面试题及答案
  3. (6)nginx:rewrite
  4. 'staticfiles' is not a registered tag library. Must be one of:
  5. access考试素材_NCRE考试当天常见问题处理办法及各科目注意事项大汇总
  6. 数据库基础知识——存储过程和函数
  7. 20165322 第二周结队编程-四则运算
  8. 【MyBatis-Plus】第一章 快速入门
  9. 我的Java之路(7)
  10. 03-21 webview 性能分析
  11. 关于char, wchar_t, TCHAR, _T(),L,宏 _T、TEXT,_TEXT、L
  12. ruby gem 记录
  13. 爬取mm131套图并下载到本地
  14. Qt项目--截屏软件
  15. 易基因|文献科普:MeRIP-seq揭示m6A RNA甲基化改变导致亨廷顿病(HD)小鼠海马记忆障碍
  16. 图片怎么转换成pdf格式?
  17. unity实现对话控制
  18. 如何清除ug服务器注册码,UG许可证删除不掉的解决方法
  19. multithreading coding
  20. 有关海盗湾墙外世界网站收藏

热门文章

  1. Debian下的内核编译
  2. Docker docker-compose 配置lnmp开发环境
  3. python 基础 7.1 datetime 获得时间
  4. 华为助力“沙漠奇迹”成为高密互联的高尔夫球场
  5. 准备上线,切换到master分支,报错
  6. 黑幕背后的Autorelease
  7. Custom Sublime Text Build Systems For Popular Tools And Languages
  8. ISA2000资料大全(详细)
  9. 使用ajaxfileupload.js上传文件成功之后,没有执行success方法
  10. 鸿蒙系统30个G,鸿蒙系统升级,为何固定大小有5.9G,也有3点几G呢?