日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

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

發(fā)布時間:2025/3/18 编程问答 20 豆豆
生活随笔 收集整理的這篇文章主要介紹了 记一次Nacos的issue修复之并发导致的NPE异常 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

ISSUE

Spring boot 應(yīng)用啟動被終止 #21

錯誤分析

DeferredApplicationEventPublisher的繼承關(guān)系

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> {... } 復(fù)制代碼

DeferredApplicationEventPublisher的依賴圖

現(xiàn)在來分析具體出現(xiàn)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)); } 復(fù)制代碼

然后看DelegatingEventPublishingListener代碼的繼承關(guān)系

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;} } 復(fù)制代碼

可以看到,在創(chuàng)建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);}// 執(zhí)行回調(diào)之前先將線程classloader設(shè)置為具體webapp的classloader,以免回調(diào)方法中調(diào)用spi接口是出現(xiàn)異常或錯用(多應(yīng)用部署才會有該問題)。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();}}... } 復(fù)制代碼

這里看到,safeNotifyListener是將事件廣播給所有的Listener,然后有一段及其重要的代碼段,它就是導(dǎo)致LinkedList出現(xiàn)并發(fā)使用的原因

listener.getExecutor().execute(job); 復(fù)制代碼

這里還記得剛剛說過的DelegatingEventPublishingListener對象在創(chuàng)建之初有傳入Executor參數(shù)嗎?這里L(fēng)istener調(diào)用Executor將上述的任務(wù)調(diào)入線程池中進行調(diào)度,因此,導(dǎo)致了DeferredApplicationEventPublisher可能存在并發(fā)的使用

錯誤復(fù)現(xiàn)

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();}}} 復(fù)制代碼

最終修正

由于是非線程安全使用在并發(fā)的場景下,因此只能更改上層nacos-spring-context的容器使用,將原先的非線程安全的LinkedList轉(zhuǎn)為線程安全的ConcurrentLinkedQueue

轉(zhuǎn)載于:https://juejin.im/post/5cee66f66fb9a07eeb138b55

總結(jié)

以上是生活随笔為你收集整理的记一次Nacos的issue修复之并发导致的NPE异常的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。