首页 > 解决方案 > WeakHashMap 交换值和键 + 更好的竞争保护

问题描述

嘿,我想要一个 WeakHashMap,其中 WeekReference 在值中,或者我正在寻找这种方法的替代方法:

我当前用于交换 WeakHashMap 的代码如下:

@Singleton
public class UUIDToResultSetMapper
{

    private final WeakHashMap<ResultSet, String> map = new WeakHashMap<>();
    private final Object lockObject = new Object();


    public void put(String key, ResultSet value)
    {
        System.out.println("current map size at put:" + this.map.size());
        synchronized (this.lockObject)
        {
            if (this.map.entrySet()
                .stream()
                .filter(element -> element.getValue().equals(value))
                .findFirst()
                .isPresent())
            {
                throw new RuntimeException("UUID is still in the map");
            }

            System.out.println("putted into map: " + key);
            this.map.put(value, key);


        }
    }

    public ResultSet get(String key)
    {
        synchronized (this.lockObject){}// memory barrier

        System.out.println("current map size at get:" + this.map.size());
   
        System.out.println("Currently in the map");
        this.map.values().stream().forEach((element) ->
        {System.out.println(element);});


        System.out.println("search for: " + key);


        final var resultSet = this.map.entrySet()
            .stream()
            .filter(element -> element.getValue().equals(key))
            .findFirst()
            .map(element -> element.getKey())
            .orElse(null);

        return resultSet;

    }
}

这里的想法是第二个请求可以中止结果集,我从中流一些数据,但我需要流的结尾,所以我可以杀死流中间的数据。

因此,每个请求都会获得一个 transaction-id (uuid),然后如果客户端认为他有足够的发送中止信号,则可以发送。所以我想让结果集在 Weakmap 中,只要结果集是活动的而不是垃圾收集。

是的,我现在可以将数据从我的 StreamingProcessor 类中放入法线贴图中,但是我必须 100% 确定我的所有资源都在试用或类似的范围内。

所以我选择了这个带有 WeakReference 的解决方案。但是我怎样才能使用 Wea​​kReferences 来避免像我一样弯曲 WeakHasMap 呢?

然后是一些离题的问题:是否有更好的解决方案可以确保在 put-method 中不受竞争条件的影响

是否有更好的解决方案可以让我在 get 方法中发生之前的关系以使可能的更改可见?(种族在这种方法中是允许的,所以结果集存在就可以了,如果不是也可以^^)

标签: javamultithreadinghashmaprace-conditionweak-references

解决方案


像这样的语句synchronized (this.lockObject){}不会形成“内存屏障”(Java 语言规范甚至不知道这个术语),而是一厢情愿的想法。

由于您希望具有弱值并且WeakHashMap不提供此功能,因此您可以简单地停止尝试弯曲它并ConcurrentHashMap首先使用 a ,以获得真正的线程安全。添加对弱值的处理ConcurrentHashMap比尝试反转键值逻辑更简单(也更有效)。

private final ConcurrentHashMap<String, WeakReference<ResultSet>> map
                                                           = new ConcurrentHashMap<>();

public void put(String key, ResultSet value) {
    map.compute(key, (k, oldRef) -> {
        if (oldRef != null && !oldRef.refersTo(null)) {
            throw new RuntimeException("UUID is still in the map");
        }
        return new WeakReference<>(value);
    });
}

public ResultSet get(String key) {
    var ref = map.computeIfPresent(key, (k, r) -> r.refersTo(null)? null: r);
    return ref == null? null: ref.get();
}

public void cleanup() {
    map.values().removeIf(ref -> ref.refersTo(null));
}

在 JDK 16 之前,您必须将其替换refersTo(null)get() == null有问题的,因为它可能会暂时使所指对象强可达。已refersTo精确添加 以避免此问题。

cleanup()方法删除收集值的所有条目。您可以在 everygetputcall 中调用此方法,类似于在WeakHashMap内部调用的方法。但由于ConcurrentHashMap是线程安全的,您也可以在后台线程中定期运行此清理。


但这仅仅是为了教育目的,从字面上解决你的问题。强烈建议不要在实际应用程序中使用此逻辑。

问题是使用此代码的应用程序的正确性将取决于垃圾收集。ResultSet当且仅当垃圾收集器已将旧密钥识别为不可访问并清除引用时,才会接受为以前使用的密钥设置新值。但是不能保证垃圾收集器在最后一次使用对象和尝试将新值放入此映射之间运行。也不能保证垃圾收集器将这个特定对象识别为不可访问,即使它运行也是如此。

即使 a 的不可达性ResultSet导致 a 的立即清除WeakReference,我们仍然会遇到这样的问题close(),即通常标志着使用结束的调用与对象变得不可达是不同的。

因此,为了确保put尝试不会虚假失败,在随后的 put 之前无法明确删除旧条目。然后,忘记显式删除将是一个严重的编程错误,如果垃圾收集器碰巧收集了值,通过偶尔修复它来隐藏这个错误,会使问题变得更糟,因为它降低了可重复性。这使得那些喜欢不时发生在客户身上的问题之一,但从不在开发人员的机器上。

但是,当忘记显式删除被认为是与在仍然使用旧值时尝试放置新值相同类别的错误时,您根本不需要弱引用。一个简单的ConcurrentHashMap<String, ResultSet>就可以了。


推荐阅读