You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

值对键有强引用场景下的Java缓存实现方案问询

问题与解答

问题背景

原Some类代码

class Some {

    static class Nullable extends Some {

        Nullable(Some wrapped) { this.wrapped = wrapped; }

        @Override public void doSome() {}

        final Some wrapped;
    }

    Some nullable() {
        return new Nullable(this);
    }

    public void doSome() {}
}

注:原代码中nullable()方法缺少return语句,已修正。

需要为Some实例及其nullable()方法返回值实现缓存。由于缓存值(Nullable实例)持有原Some实例的强引用,是否不能使用WeakHashMap?如何在不修改Some类、不使用第三方库的前提下实现缓存?以下尝试的实现是否可行?

尝试的缓存实现代码

private static final Map<Some, Some> MAP = new HashMap<>(); // TODO: synchronize!

private static final ReferenceQueue<Some> QUEUE = new ReferenceQueue<>();

private static volatile Thread thread = null;

@SuppressWarnings({"unchecked"})
static Some get(final Some some) {
    synchronized (TheClass.class) {
        if (thread == null) {
            thread = new Thread(() -> {
                while (true) {
                    try {
                        Reference<? extends Some> reference = QUEUE.remove();
                        Some key = reference.get(); // can be null?
                        Some value = MAP.remove(key);
                    } catch (final InterruptedException ie) {
                        Thread.currentThread().interrupt();
                    }
                }
            });
            thread.setDaemon(true);
            thread.start();
        }
    }
    return (Some) MAP.computeIfAbsent(some, k -> {
        final boolean enqueued = new WeakReference<>(k, QUEUE).enqueue();
        assert enqueued;
        return k.nullable();
    });
}

注:原代码中返回类型(T)为错误泛型,已修正为(Some)。

后续问题

reference.get()是否真的会返回null?


解答

关于WeakHashMap的疑问

没错,不能直接使用WeakHashMap。WeakHashMap的弱引用仅作用于键,但这里缓存值(Nullable实例)持有原Some实例的强引用,形成了缓存值 → 原Some实例 → WeakHashMap键的强引用链,导致原Some实例永远不会被GC回收,WeakHashMap的键也不会被自动移除,最终会造成缓存无限膨胀、内存泄漏。

尝试的实现是否可行?

思路方向正确,但存在几个致命问题:

  1. 线程安全缺失:HashMap不是线程安全容器,仅同步线程初始化部分无法覆盖缓存读写的并发场景,computeIfAbsent在多线程下会出现竞态条件,导致重复创建Nullable实例或缓存数据不一致。
  2. WeakReference无效:在computeIfAbsent中新建的WeakReference没有被持久化存储,会很快被GC回收,无法跟踪原Some实例的生命周期,ReferenceQueue的清理逻辑完全失效。
  3. 清理逻辑错误:当从ReferenceQueue取出引用时,reference.get()确实可能返回null(原对象已被GC回收),此时用null作为键调用MAP.remove(key)无法清除缓存中的对应条目,因为缓存键是原Some实例的强引用,这会导致缓存条目永远无法被清理,引发内存泄漏。

正确的缓存实现方案

可以通过HashMap<WeakReference<Some>, Some>配合ReferenceQueue实现,核心是用WeakReference作为缓存键,跟踪原Some实例的生命周期:

private static final Map<WeakReference<Some>, Some> CACHE = Collections.synchronizedMap(new HashMap<>());
private static final ReferenceQueue<Some> REF_QUEUE = new ReferenceQueue<>();
private static volatile Thread cleanerThread;

// 初始化清理线程
static {
    cleanerThread = new Thread(() -> {
        while (!Thread.currentThread().isInterrupted()) {
            try {
                // 取出被GC回收的WeakReference
                WeakReference<Some> ref = (WeakReference<Some>) REF_QUEUE.remove();
                // 移除对应缓存条目
                CACHE.remove(ref);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
    });
    cleanerThread.setDaemon(true);
    cleanerThread.start();
}

public static Some getNullable(Some some) {
    // 遍历缓存查找匹配的原实例
    for (Map.Entry<WeakReference<Some>, Some> entry : CACHE.entrySet()) {
        Some cachedKey = entry.getKey().get();
        if (cachedKey != null && cachedKey == some) {
            return entry.getValue();
        }
    }
    // 缓存不存在则创建并放入
    Some nullable = some.nullable();
    WeakReference<Some> keyRef = new WeakReference<>(some, REF_QUEUE);
    CACHE.put(keyRef, nullable);
    return nullable;
}

该方案的优势:

  • 用WeakReference<Some>作为缓存键,原Some实例无其他强引用时,WeakReference会被GC回收并进入ReferenceQueue
  • 后台清理线程监听ReferenceQueue,及时移除失效的缓存条目,避免内存泄漏
  • 用Collections.synchronizedMap保证缓存操作的线程安全

关于reference.get()是否返回null的问题

是的,reference.get()确实会返回null。当GC准备回收原Some实例时,会先将WeakReference的referent字段置为null,再将该WeakReference放入ReferenceQueue。因此当从队列中取出这个WeakReference时,调用get()得到的就是null。这也是为什么需要用WeakReference作为缓存键,而非原对象——即使referent为null,依然可以通过WeakReference本身定位并移除缓存条目。


内容的提问来源于stack exchange,提问作者Jin Kwon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 15:15:38