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

如何实现键值互换WeakHashMap并优化竞态保护与内存可见性

实现方案与问题解答

完整优化后实现代码

@Singleton
public class UUIDToResultSetMapper {
    // 引用队列,接收被GC回收的ResultSet对应的弱引用
    private final ReferenceQueue<ResultSet> refQueue = new ReferenceQueue<>();
    // 核心存储:键为UUID(强引用),值为绑定了UUID的ResultSet弱引用
    private final ConcurrentHashMap<String, ResultSetWeakRef> map = new ConcurrentHashMap<>();

    // 自定义弱引用子类,绑定对应UUID,用于GC后清理Map中的无效条目
    private static class ResultSetWeakRef extends WeakReference<ResultSet> {
        private final String uuid;

        public ResultSetWeakRef(String uuid, ResultSet referent, ReferenceQueue<ResultSet> queue) {
            super(referent, queue);
            this.uuid = uuid;
        }

        public String getUuid() {
            return uuid;
        }
    }

    // 清理已被GC的无效条目,每次读写操作前调用即可,无额外线程开销
    private void clearInvalidEntries() {
        ResultSetWeakRef ref;
        while ((ref = (ResultSetWeakRef) refQueue.poll()) != null) {
            map.remove(ref.getUuid());
        }
    }

    public void put(String key, ResultSet value) {
        clearInvalidEntries();
        // compute为原子操作,天然避免竞态,无需额外加锁
        map.compute(key, (k, existingRef) -> {
            // 原逻辑:UUID已存在且对应ResultSet未被GC则抛出异常
            if (existingRef != null && existingRef.get() != null) {
                throw new RuntimeException("UUID is still in the map");
            }
            System.out.println("put into map: " + key);
            return new ResultSetWeakRef(key, value, refQueue);
        });
        System.out.println("current map size at put:" + map.size());
    }

    public ResultSet get(String key) {
        clearInvalidEntries();
        System.out.println("current map size at get:" + map.size());
        // 打印当前所有有效UUID
        map.values().forEach(ref -> {
            if (ref.get() != null) {
                System.out.println(ref.getUuid());
            }
        });
        System.out.println("search for: " + key);
        ResultSetWeakRef ref = map.get(key);
        return ref != null ? ref.get() : null;
    }
}

对应问题解答

1. 基于WeakReference的合规实现

你原有实现是通过键值互换借用WeakHashMap键弱引用的能力,属于hack用法。合规实现逻辑如下:

  • 用UUID作为Map的键,直接强引用保留
  • 自定义WeakReference子类,同时持有ResultSet和对应的UUID,将ResultSet设为弱引用目标
  • 搭配ReferenceQueue接收被GC回收的ResultSet对应的弱引用实例
  • 每次读写操作前拉取ReferenceQueue中的无效引用,删除Map中对应的条目

该实现完全符合需求:只要ResultSet被业务逻辑持有强引用处于活跃状态,对应条目就会保留;一旦ResultSet被GC回收,对应条目会被自动清理,无需手动释放。

2. put方法防竞态优化方案

原有实现存在两个明显问题:一是为了校验UUID是否存在需要遍历全表,时间复杂度为O(n),性能极差;二是通过全局synchronized加锁,并发度低。
优化方案:

  • 直接用UUID作为Map的键,存在性校验从O(n)降为O(1)
  • 用ConcurrentHashMap的compute方法完成校验+插入的原子操作,无需额外加锁,天然避免竞态,并发性能远高于全局synchronized锁。如果不需要严格的重复插入校验,还可以用性能更高的putIfAbsent方法。

3. get方法happens-before关系实现

原有实现中空的synchronized块属于非常不规范的内存屏障用法,性能损耗高且可读性差。
最优方案是直接使用ConcurrentHashMap作为存储容器,Java并发规范明确保证:ConcurrentHashMap的写入操作 happens-before 后续对同一位置的读取操作,所有变更天然对读线程可见,完全不需要手动加任何内存屏障。
如果你确实不想使用ConcurrentHashMap,也可以用Java 9+提供的VarHandle.fullFence()手动加内存屏障代替空synchronized块,但复杂度和收益完全不成正比,不推荐使用。


内容的提问来源于stack exchange,提问作者Robin Kreuzer

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.26 18:24:00