Java緩存數(shù)據(jù)讀取失?。红o態(tài)變量與單例模式的陷阱及解決方案?

Java緩存數(shù)據(jù)讀取失?。红o態(tài)變量與單例模式的陷阱及解決方案?

Java緩存數(shù)據(jù)讀取失?。罕苊忪o態(tài)變量和單例模式的陷阱

Java應(yīng)用中,緩存大量數(shù)據(jù)以提升性能是常見(jiàn)做法。然而,有時(shí)會(huì)遇到從緩存中讀取數(shù)據(jù)失敗的問(wèn)題。本文分析一個(gè)案例,探討導(dǎo)致Java緩存數(shù)據(jù)讀取失敗的原因,并提供解決方案。

案例:內(nèi)存不足導(dǎo)致緩存數(shù)據(jù)丟失

開(kāi)發(fā)者使用scenariobuffer類(lèi)將約16萬(wàn)條資產(chǎn)數(shù)據(jù)加載到名為assetbuffer的HashMap中。getbasset方法用于讀取緩存數(shù)據(jù)。在服務(wù)器內(nèi)存不足(可用內(nèi)存僅剩100MB,緩存占用3GB,總內(nèi)存8GB)時(shí),getbasset方法返回空值。重啟服務(wù)器并清除緩存后,問(wèn)題解決。項(xiàng)目使用tomcat啟動(dòng),分配約3GB內(nèi)存。代碼如下:

立即學(xué)習(xí)Java免費(fèi)學(xué)習(xí)筆記(深入)”;

@Component @Order(1) @Slf4j public class scenariobuffer implements IActionListener, ApplicationRunner {     private Static Map<String, List<Asset>> assetbuffer = Collections.synchronizedMap(new HashMap<>());     private static scenariobuffer instance = new scenariobuffer();      public static scenariobuffer getinstance() {         return instance;     }      public static List<Asset> getbasset(String groupid) {         if (assetbuffer.containsKey(groupid)) {             return assetbuffer.get(groupid);         }         return null;     }      @Override     public void run(ApplicationArguments args) throws Exception {         IAssetService assetService = springUtil.getBean(IAssetService.class);         List<Asset> assetlist = assetService.list();         assetbuffer.put("key", assetlist);     } }

問(wèn)題分析與解決方案

問(wèn)題并非單純的內(nèi)存不足導(dǎo)致jvm清空數(shù)據(jù),而是代碼設(shè)計(jì)缺陷:

  1. 不當(dāng)使用static和getinstance(): scenariobuffer類(lèi)使用static關(guān)鍵字,使其成為單例。但getinstance()方法冗余,因?yàn)镾pring容器已保證@Component注解的Bean是單例的。static關(guān)鍵字導(dǎo)致assetbuffer成為靜態(tài)變量,生命周期與應(yīng)用服務(wù)器相同,即使JVM垃圾回收,也難以回收其內(nèi)存。

  2. 不推薦的Bean獲取方式: 使用SpringUtil.getBean(IAssetService.class)獲取Bean,不推薦。Spring框架推薦使用@Autowired或@Resource注解進(jìn)行依賴(lài)注入。

  3. 初始化時(shí)機(jī)問(wèn)題: 使用ApplicationRunner接口在應(yīng)用啟動(dòng)時(shí)初始化緩存,但更好的方式是使用@PostConstruct注解或?qū)崿F(xiàn)InitializingBean接口,使初始化過(guò)程更明確可控。

改進(jìn)后的代碼

修改scenariobuffer類(lèi)及其使用方法:

@Component public class scenariobuffer implements IActionListener {     private Map<String, List<Asset>> assetbuffer = new HashMap<>();      @Autowired     private IAssetService assetservice;      @PostConstruct     public void init() {         List<Asset> assetlist = assetservice.list();         assetbuffer.put("key", assetlist);     }      public List<Asset> getbasset(String groupid) {         return assetbuffer.get(groupid);     } }

在其他服務(wù)類(lèi)中,使用@Resource注解注入scenariobuffer:

@Service public class XxxService {     @Resource     private ScenarioBuffer scenarioBuffer;      public void xxx() {         List<Asset> asset = scenarioBuffer.getBAsset("xxx");     } }

改進(jìn)后,避免了不必要的靜態(tài)變量和單例獲取方法,利用Spring框架的依賴(lài)注入機(jī)制和@PostConstruct注解,使代碼更清晰、易于維護(hù),并減少了內(nèi)存泄漏的風(fēng)險(xiǎn)。即使服務(wù)器內(nèi)存緊張,JVM垃圾回收機(jī)制也能更有效工作,降低緩存數(shù)據(jù)讀取失敗的可能性。

? 版權(quán)聲明
THE END
喜歡就支持一下吧
點(diǎn)贊15 分享
站長(zhǎng)的頭像-小浪學(xué)習(xí)網(wǎng)月度會(huì)員