通过Map#compute添加WeakReference到缓存:是否会返回null?
问题解答
1. 解引用是否可能返回null?
肯定会。原因有两点:
- 当
varFactory创建出WeakVAR时,内部的V实例仅被弱引用指向。如果在调用get()解引用前触发了GC,这个V实例会被直接回收,get()就会返回null。 - 在
ConcurrentHashMap.compute()执行完成,到调用返回的WeakVAR.get()的间隙,GC也可能刚好回收新创建的V实例,导致结果为null。
2. 最佳解决方式
核心思路是确保V实例在返回给调用者前有强引用持有,避免被GC提前回收。可以调整逻辑如下:
Supplier<V> vFactory = () -> { // 先创建实例并持有强引用,避免被GC提前回收 V data = factory.apply(id); VAR<V> newVar = new WeakVAR(data, id, version); // 原子替换缓存中的值 VAR<V> finalVar = cache.compute(id, (key, existing) -> { if (existing == null) { return newVar; } V existingData = existing.get(); // 仅当旧实例已回收或版本更低时,才替换为新实例 if (existingData == null || existing.getVersion() < version) { return newVar; } // 若已有更高版本实例,保留原有值,新创建的data会自动被GC回收 return existing; }); // 如果最终缓存的是我们刚创建的实例,直接返回强引用的data;否则返回缓存实例的引用 return finalVar == newVar ? data : finalVar.get(); };
另外,建议启动一个后台线程定期处理ReferenceQueue,清理缓存中已失效的WeakVAR,避免缓存积累大量无效引用占用内存。
3. 忙循环是否为恰当解决方案?
完全不恰当。忙循环会无意义地占用CPU资源,若GC持续频繁触发,还可能导致循环一直重试,浪费系统资源。如果确实需要重试逻辑,也应该加入短暂休眠(比如Thread.sleep(10)),但最优方案还是通过强引用保护新创建的实例,从根源上避免null的出现。
内容的提问来源于stack exchange,提问作者Andrey B. Panfilov
相关产品推荐
相关产品推荐

