接口没获取到就被使用_使用CompletableFuture时,那些令人头疼的问题
背景
有一个功能,这个功能里需要调用几个不同的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时,那些令人头疼的问题相关推荐
- wordpress 外部数据接口_使用接口方式获取WordPress用户信息的方法
今天WordPress主题站简单介绍一下WordPress系统中用户信息获取方式,今天就讲讲使用接口方式获取WordPress用户信息的方法. 接口文件如下: if ('POST' != $_SERV ...
- mysql的ole db 访问接口msdasql的数据源对象_无法从链接服务器 (null) 的 OLE DB 访问接口 MSDASQL 获取列信息...
无法从链接服务器"(null)"的OLEDB访问接口"MSDASQL"获取列信息.链接服务器"(null)"的OLEDB访问接口" ...
- 调用后台接口返回报错前端隐藏提示_腾讯社交联盟广告
开发者帮助中心 优量汇服务体系升级了,除查阅本页常见问题外,还可以通过以下渠道解决您遇到的问题 1. 实时智能客服 入口:优量汇官网.开发者平台.优量汇服务号 时间:7*24即时问答 服务内容:涵盖 ...
- 股市量化交易接口如何获取A股历史数据?
股市量化交易接口其实也是对散户开放等是比较安全稳定接口,但是其接口通过第三方券商完成交易,主要用做于个人或机构做私募等量化投资数据参考的首选,比如说在股市中进行量化投资时,通过接口策略的定制将股票数据 ...
- 计算机usb端口没反应,技术编辑教您电脑usb接口没反应怎么办
近来,有好多小伙伴反应有电脑USB接口不能使用的情况,鼠标键盘通通没反应.针对电脑usb接口没反应的问题,小编整理了常见的原因以及解决方法,希望能帮助你们解决问题 USB是一个外部总线标准,用于规范电 ...
- python excel token_python+excel接口自动化获取token并作为请求参数进行传参操作
1.登录接口登录后返回对应token封装: import json import requests from util.operation_json import OperationJson from ...
- jq动态渲染后获取不到元素高度_浏览器的渲染机制
面试肯定会问到这个吧~ So:再一次的屡屡浏览器的渲染机制~ 在渲染一开始会先从网络层获取请求文档(HTML.XML)的内容,然后再进行以下基本流程 3.1 解析HTML => DOM树 从HT ...
- 沪深股票接口如何获取所有股票代码?
沪深股票接口如何获取所有股票代码呢?对于这个问题,相信大家也有同样的疑问,毕竟涉及到股票交易,运用自动化交易系统进行查询股票代码数据也是很直接的方法,那么接下来,小编就把沪深股票接口获取股票代码的程序 ...
- 东财量化接口怎么获取?
东财量化接口怎么获取? 第一种方法就是自己懂编程,可以自己做一个出来,但是这样会有很大的问题就是不一定会符合券商平台的,就是不兼容的问题. 第一种方法就是找到相对应的平台,进行一个咨询,入手一个正规的 ...
- 计算机usb端口没反应,解决电脑USB接口没反应的小方法
很多用户在使用电脑的时候,都需要用到移动设备等,但是不少用户在使用的过程中,总是会碰到各种问题.比如将U盘插入电脑的时候,电脑根本没有任何的反映,这是怎么回事呢?电脑USB接口没反应要怎么办?今天U大 ...
最新文章
- MFC之进度条CProgressCtrl
- 五轮阿里面试题及答案
- (6)nginx:rewrite
- 'staticfiles' is not a registered tag library. Must be one of:
- access考试素材_NCRE考试当天常见问题处理办法及各科目注意事项大汇总
- 数据库基础知识——存储过程和函数
- 20165322 第二周结队编程-四则运算
- 【MyBatis-Plus】第一章 快速入门
- 我的Java之路(7)
- 03-21 webview 性能分析
- 关于char, wchar_t, TCHAR, _T(),L,宏 _T、TEXT,_TEXT、L
- ruby gem 记录
- 爬取mm131套图并下载到本地
- Qt项目--截屏软件
- 易基因|文献科普:MeRIP-seq揭示m6A RNA甲基化改变导致亨廷顿病(HD)小鼠海马记忆障碍
- 图片怎么转换成pdf格式?
- unity实现对话控制
- 如何清除ug服务器注册码,UG许可证删除不掉的解决方法
- multithreading coding
- 有关海盗湾墙外世界网站收藏
热门文章
- Debian下的内核编译
- Docker docker-compose 配置lnmp开发环境
- python 基础 7.1 datetime 获得时间
- 华为助力“沙漠奇迹”成为高密互联的高尔夫球场
- 准备上线,切换到master分支,报错
- 黑幕背后的Autorelease
- Custom Sublime Text Build Systems For Popular Tools And Languages
- ISA2000资料大全(详细)
- 使用ajaxfileupload.js上传文件成功之后,没有执行success方法
- 鸿蒙系统30个G,鸿蒙系统升级,为何固定大小有5.9G,也有3点几G呢?