ISSUE

Spring boot 应用启动被终止 #21

错误分析

DeferredApplicationEventPublisher的继承关系

import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.ContextRefreshedEvent;public class DeferredApplicationEventPublisher implements ApplicationEventPublisher, ApplicationListener<ContextRefreshedEvent> {...
}
复制代码

DeferredApplicationEventPublisher的依赖图

现在来分析具体出现NPE错误的原因

先看EventPublishingConfigService中的addListener

@Override
public void addListener(String dataId, String group, Listener listener) throws NacosException {Listener listenerAdapter = new DelegatingEventPublishingListener(configService, dataId, group, applicationEventPublisher, executor, listener);configService.addListener(dataId, group, listenerAdapter);publishEvent(new NacosConfigListenerRegisteredEvent(configService, dataId, group, listener, true));
}
复制代码

然后看DelegatingEventPublishingListener代码的继承关系

import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import org.springframework.context.ApplicationEventPublisher;import java.util.concurrent.Executor;final class DelegatingEventPublishingListener implements Listener {DelegatingEventPublishingListener(ConfigService configService, String dataId, String groupId, ApplicationEventPublisher applicationEventPublisher, Executor executor, Listener delegate) {this.configService = configService;this.dataId = dataId;this.groupId = groupId;this.applicationEventPublisher = applicationEventPublisher;this.executor = executor;this.delegate = delegate;}
}
复制代码

可以看到,在创建DelegatingEventPublishingListener对象的时候,会传入一个线程池Executor,以及一个ApplicationEventPublisher(其实就是DeferredApplicationEventPublisher

然后再看看CacheData.safeNotifyListener()方法做了什么操作

private void safeNotifyListener(final String dataId, final String group, final String content, final String md5, final ManagerListenerWrap listenerWrap) {final Listener listener = listenerWrap.listener;Runnable job = new Runnable() {public void run() {ClassLoader myClassLoader = Thread.currentThread().getContextClassLoader();ClassLoader appClassLoader = listener.getClass().getClassLoader();try {if (listener instanceof AbstractSharedListener) {AbstractSharedListener adapter = (AbstractSharedListener)listener;adapter.fillContext(dataId, group);LOGGER.info("[{}] [notify-context] dataId={}, group={}, md5={}", name, dataId, group, md5);}// 执行回调之前先将线程classloader设置为具体webapp的classloader,以免回调方法中调用spi接口是出现异常或错用(多应用部署才会有该问题)。Thread.currentThread().setContextClassLoader(appClassLoader);ConfigResponse cr = new ConfigResponse();cr.setDataId(dataId);cr.setGroup(group);cr.setContent(content);configFilterChainManager.doFilter(null, cr);String contentTmp = cr.getContent();listener.receiveConfigInfo(contentTmp);listenerWrap.lastCallMd5 = md5;LOGGER.info("[{}] [notify-ok] dataId={}, group={}, md5={}, listener={} ", name, dataId, group, md5,listener);} catch (NacosException de) {LOGGER.error("[{}] [notify-error] dataId={}, group={}, md5={}, listener={} errCode={} errMsg={}", name,dataId, group, md5, listener, de.getErrCode(), de.getErrMsg());} catch (Throwable t) {LOGGER.error("[{}] [notify-error] dataId={}, group={}, md5={}, listener={} tx={}", name, dataId, group,md5, listener, t.getCause());} finally {Thread.currentThread().setContextClassLoader(myClassLoader);}}};final long startNotify = System.currentTimeMillis();try {if (null != listener.getExecutor()) {listener.getExecutor().execute(job);} else {job.run();}}...
}
复制代码

这里看到,safeNotifyListener是将事件广播给所有的Listener,然后有一段及其重要的代码段,它就是导致LinkedList出现并发使用的原因

listener.getExecutor().execute(job);
复制代码

这里还记得刚刚说过的DelegatingEventPublishingListener对象在创建之初有传入Executor参数吗?这里Listener调用Executor将上述的任务调入线程池中进行调度,因此,导致了DeferredApplicationEventPublisher可能存在并发的使用

错误复现

public class DeferrNPE {private static LinkedList<String> list = new LinkedList<>();private static CountDownLatch latch = new CountDownLatch(3);private static CountDownLatch start = new CountDownLatch(3);private static class MyListener implements Runnable {@Overridepublic void run() {start.countDown();try {start.await();} catch (InterruptedException e) {e.printStackTrace();}list.add(String.valueOf(System.currentTimeMillis()));latch.countDown();}}public static void main(String[] args) {MyListener l1 = new MyListener();MyListener l2 = new MyListener();MyListener l3 = new MyListener();new Thread(l1).start();new Thread(l2).start();new Thread(l3).start();try {latch.await();Iterator<String> iterator = list.iterator();while (iterator.hasNext()) {System.out.println(iterator.next());iterator.remove();}} catch (InterruptedException e) {e.printStackTrace();}}}
复制代码

最终修正

由于是非线程安全使用在并发的场景下,因此只能更改上层nacos-spring-context的容器使用,将原先的非线程安全的LinkedList转为线程安全的ConcurrentLinkedQueue

转载于:https://juejin.im/post/5cee66f66fb9a07eeb138b55

记一次Nacos的issue修复之并发导致的NPE异常相关推荐

  1. 记一次触发器定义者不同导致的sql异常TRIGGER command denied to user 'XXX' @'%' for table '...

    记一次触发器定义者不同导致的sql异常 触发器:在执行某一类型的sql后触发其他已经提前写好的sql 先上图 这是打印出的错误日志信息 图中weidong_rwser_test_temp为数据库连接账 ...

  2. 记一次失败的MySQL修复经历,报错信息:Tablespace X was not found at X;Set innodb_force_recovery=1 to ignore this

    事情的起因 首先前一天晚上我跑了一个not in语法的SQL语句,因为正好是下班了就让它自己跑去了,结果一不小心造成了笛卡尔积,第二天发现空间爆了,于是开始正常标准操作:暂停,查看进程,发现InnoD ...

  3. 阿里的nacos+springboot+dubbo2.7.3集成以及统一处理异常的两种方式

    在网上很多关于dubbo异常统一处理的博文,90%都是抄来抄去.大多都是先上一段dubbo中对于异常的统一处理的原码,然后说一堆的(甚至有12345,五种)不靠谱方案,最后再说"本篇使用的是 ...

  4. 360修复导致服务器,桌面安装360软件修复漏洞补丁导致桌面TC端无法登陆,FC端VNC登陆一键修复显示HDC不可达...

    问题描述 桌面虚机安装360软件以后打补丁,触发桌面虚机重启以后TC端无法连接到桌面,FC登陆VNC相应的虚拟机通过桌面云修复工具一键修复到33%,提示HDC不可达. 告警信息 处理过程 在360服务 ...

  5. 使用Nacos项目jar包启动抛出的yml异常

    记录一下项目jar包启动时一直抛出nacos yml编译错误的问题 一开始抛出yml的问题,是编码问题,但是又不知道Nacos里怎么配置编码格式. 所以我只能把nacos中配置文件里的注释含泪删除调试 ...

  6. 修复Jscript(IE浏览器脚本引擎)异常

    今天IE出现不能处理JS脚本的问题,一开始怀疑IE坏了,但不知道具体坏在哪. 想到了重装IE,上网搜"重装IE",结果给出的方法非常复杂,手头又没有Windows XP安装盘 想想 ...

  7. 修复iPhone系统故障导致的黑屏

    iPhone手机屏幕黑屏,如果没有硬件问题的话,那就是手机系统出现了问题,需要对iOS系统进行修复,我们可以使用iTunes,打开软件,将手机接入电脑后,选择"更新"或者" ...

  8. java response下载docx,报文件损坏是否修复,ContentLength导致的

    使用http response编写附件下载功能,当附件是docx时,用office的word打开,会出现文件损坏,是否修复的错误,点击修复又能正常打开. 首先,仔细对比文件发现,下载下来的文件比服务器 ...

  9. 装完nvme固态经常蓝屏_Win10 KB4586853修复NVMe SSD导致蓝屏死机问题

    微软此前发布支持公告确认当Windows 10连接NVMe协议的固态硬盘时,有可能会因为渠道问题引发蓝屏死机.该问题理论上主要影响的是附带Thunderbolt接口的设备,当用户将固态盘通过该接口连接 ...

最新文章

  1. Complex Instance Placement
  2. 面试投行的20个Java问题
  3. WCF扩展:行为扩展Behavior Extension一
  4. 安装ubuntu20.4+gtx1050+cuda11.3
  5. Facebook上的一道题,超过50万的评论和1万3500次分享
  6. git merge 冲突_卧槽!小姐姐用动画图解 Git 命令,这也太秀了吧?!
  7. 如果我用你待我的方式来待你 恐怕你早已离去
  8. WebAPI Get
  9. 服务器放在机柜_机架式服务器和塔式服务器有区别吗
  10. 是的,我开始做这么一件事了
  11. centos Linux 上 怎么命令行安装和卸载QQ
  12. 《Git与Github使用笔记》分享3款Git可视化工具
  13. matlab如何从视频中分离音频文件,如何从视频中分离音频文件 值得收藏
  14. windows便签快捷键_超级实用的Windows快捷键
  15. 常见js针对浏览器之间的兼容问题
  16. background-size属性详解
  17. android 调取数字键盘,Android自定义键盘的实现(数字键盘和字母键盘)
  18. python自动排版公众号_自制微信公众号一键排版工具
  19. 【技术文档】《算法设计与分析导论》R.C.T.Lee等·第6章 剪枝搜索方法
  20. RoboCup Rescue Simulator Tutorial core

热门文章

  1. el-table-column中格式化判断数据为空则显示指定内容
  2. MyBatis中提示:Invalid Bound statemnet(not found )com.
  3. 赶紧下载SublimeText并快速设置代码自动补全,效率大大提高
  4. Vue实现仿音乐播放器项目总述以及阶段目录
  5. BJUI实现点击按钮弹窗,提交到后台action后回显数据流程整理
  6. 代码夹带是洪水猛兽吗?
  7. 4、SpringBoot 配置和使用定时任务
  8. java 序列化 例子_Java序列化和反序列化例子
  9. 神策数据正式成为国家级信创工委会成员单位
  10. 如何使用Java代码给图片增加倒影效果