迭代一个WeakHashMap

11

我正在同时使用WeakHashMap。 我想基于整数参数实现细粒度锁定;如果线程A需要修改由整数a标识的资源,而线程B也对由整数b标识的资源进行相同的操作,则它们不需要同步。但是,如果有两个线程使用同一个资源,例如线程C也在使用由整数a标识的资源,则当然线程A和C需要在同一个锁上同步。

当没有更多需要ID为X的资源的线程时,则可以删除Map中用于键=X的锁。但是,在此时可能会有另一个线程进入并尝试使用Map中ID=X的锁,因此我们需要在添加/删除锁时进行全局同步。(这将是每个线程都必须同步的唯一地方,无论整数参数如何)。但��,线程无法知道何时删除锁,因为它不知道它是否是最后一个使用锁的线程。

这就是为什么我正在使用WeakHashMap的原因:当ID不再使用时,当GC需要时,可以删除键值对。

为了确保对已存在条目的键具有强引用,并且正是形成映射的键的对象引用,我需要迭代映射的keySet:

synchronized (mrLocks){
    // ... do other stuff
    for (Integer entryKey : mrLocks.keySet()) {
        if (entryKey.equals(id)) {
            key = entryKey;
            break;
        }
    }
    // if key==null, no thread has a strong reference to the Integer
    // key, so no thread is doing work on resource with id, so we can
    // add a mapping (new Integer(id) => new ReentrantLock()) here as
    // we are in a synchronized block. We must keep a strong reference
    // to the newly created Integer, because otherwise the id-lock mapping
    // may already have been removed by the time we start using it, and 
    // then other threads will not use the same Lock object for this
    // resource
}

现在,在迭代过程中,Map的内容可以更改吗?我认为不行,因为通过调用mrLocks.keySet(),我为迭代范围内的所有键创建了强引用。这正确吗?


请参见:https://dev59.com/IHE85IYBdhLWcg3wRBRU - ikettu
1
我认为不是,来自JavaDoc:"该集合由映射支持,因此对映射的更改会反映在集合中,反之亦然。" - m0skit0
@m0skit0 哦,你可能是对的。返回的 Set 也将包含 WeakReference,但这就像 WeakHashMap 隐藏它一样。所以我应该先克隆 keySet,然后迭代克隆,我猜,以确保我正在迭代具有强引用的集合。 - Timmos
也许我误解了你的意图,但我真的看不出迭代的重要性。你要么找到条目,要么就没有。 “但是,线程无法知道何时删除锁定,因为它不知道它是使用锁定的最后一个线程...这就是为什么我正在使用WeakHashMap。” 这里有一个逻辑跳跃,我认为你没有很好地解释清楚。你的问题是关于keySet还是你正在寻求一种不使用WeakHashMap的更好设计? http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem - Radiodef
@Radiodef 我希望在不再需要时删除地图条目。让最后一个线程这样做是不可能的,所以我通过使用WeakHashMap将内存管理委托给垃圾收集器。如果我使用AtomicInteger来跟踪使用锁的线程,那么这是可能的,但是这样会有很多繁琐的工作,使它变得混乱。关键点是我想避免条目永远存在于Map中,因为那将导致内存泄漏。 - Timmos
1个回答

3
作为API没有关于keySet()的断言,我建议使用缓存时采用以下方式:
private static Map<Integer, Reference<Integer>> lockCache = Collections.synchronizedMap(new WeakHashMap<>());

public static Object getLock(Integer i)
{
    Integer monitor = null;
    synchronized(lockCache) {
        Reference<Integer> old = lockCache.get(i);
        if (old != null)
            monitor = old.get();

        // if no monitor exists yet
        if (monitor == null) {
            /* clone i for avoiding strong references 
               to the map's key besides the Object returend 
               by this method.
            */ 
            monitor = new Integer(i);
            lockCache.remove(monitor); //just to be sure
            lockCache.put(monitor, new WeakReference<>(monitor));
        }

    }

    return monitor;
}

这样,当你锁定监视器(即密钥本身)时,你会持有它的引用,并在不再使用它时允许GC对其进行终结。

编辑:
在评论中讨论了有效载荷后,我考虑了一种带有两个缓存的解决方案:

private static Map<Integer, Reference<ReentrantLock>> lockCache = new WeakHashMap<>();
private static Map<ReentrantLock, Integer> keyCache = new WeakHashMap<>();

public static ReentrantLock getLock(Integer i)
{
    ReentrantLock lock = null;
    synchronized(lockCache) {
        Reference<ReentrantLock> old = lockCache.get(i);
        if (old != null)
            lock = old.get();

        // if no lock exists or got cleared from keyCache already but not from lockCache yet
        if (lock == null || !keyCache.containsKey(lock)) {
            /* clone i for avoiding strong references 
               to the map's key besides the Object returend 
               by this method.
           */ 
            Integer cacheKey = new Integer(i); 
            lock = new ReentrantLock();
            lockCache.remove(cacheKey); // just to be sure
            lockCache.put(cacheKey, new WeakReference<>(lock));
            keyCache.put(lock, cacheKey);
        }                
    }

    return lock;
}

只要存在指向有效载荷(锁)的强引用,对于 keyCache 中映射整数的强引用可以避免从 lockCache 缓存中删除有效载荷。

你为什么要使用 Collections.synchronizedMap?已经有使用 synchronized 关键字的外部同步,所以你不需要内部同步。 - Timmos
@Timmos 复制粘贴 :-) 你是对的,它基本上没什么用,可以被移除。 - flo
1
我已经检查了你的代码,看起来足够干净,我已经考虑过这样的解决方案。现在的问题是有效载荷(一个真正的Lock对象)需要包含在值中,这就需要一个新的类来封装一个弱引用的整数和一个强引用的Lock对象,这实际上是我试图避免的。你不必迭代键来查找正确的整数实例,只需将其存储在值中即可。你的解决方案也似乎是一个有效的方法。 - Timmos
为了避免再次封装对象,您可以继承您的负载并添加对密钥的引用,@Timmos。 - flo
让我们在聊天中继续这个讨论:http://chat.stackoverflow.com/rooms/69130/discussion-between-timmos-and-flo。 - Timmos
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接