【问题标题】:Iterating a WeakHashMap迭代 WeakHashMap
【发布时间】:2015-01-19 11:11:06
【问题描述】:

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

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

这就是我使用 Wea​​kHashMap 的原因:当不再使用 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(),我为迭代范围创建了对所有键的强引用。对吗?

【问题讨论】:

  • 我认为不是,来自JavaDoc"集合由地图支持,因此对地图的更改会反映在集合中,反之亦然。"
  • @m0skit0 啊,你可能是对的。返回的 Set 也将包含 WeakReference,但这就像 WeakHashMap 隐藏它一样被隐藏。所以我应该先克隆一个 keySet,然后迭代克隆,我猜,以确保我正在迭代一个具有强引用的集合。
  • 也许我误解了你想要做的事情,但我并没有真正看到迭代的重要性。您要么找到条目,要么没有。 “但是,一个线程不知道何时移除锁,因为它不知道它是最后一个使用锁的线程......这就是我使用 Wea​​kHashMap 的原因。”这里的逻辑跳转,我认为你解释得不是很好。您是关于 keySet 的问题,还是您要求一个不使用 Wea​​kHashMap 的更好的设计? meta.stackexchange.com/questions/66377/what-is-the-xy-problem
  • @Radiodef 我希望在不再需要时删除地图条目。让最后一个线程执行此操作是不可能的,因此我通过使用 Wea​​kHashMap 将内存管理委托给垃圾收集器。好吧,如果我使用 AtomicInteger 来跟踪使用锁的线程是可能的,但是有太多的簿记要做,这使得它变得一团糟。关键是我想避免条目永远在 Map 中,因为那会导致内存泄漏。

标签: java multithreading weak-references


【解决方案1】:

由于 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 在不再使用它时完成它。

编辑:
在讨论了 cmets 中的有效负载之后,我想到了一个具有两个缓存的解决方案:

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 copy&pase :-) 你是对的,它基本上没用,可以删除。
  • 我检查了你的代码,这看起来很干净,我已经考虑过这样的解决方案。现在的问题是负载(一个真正的 Lock 对象)需要包含在值中,这需要一个新类来封装弱引用的 Integer 和强引用的 Lock 对象,这实际上是我试图做的避免。无需迭代键以查找正确的 Integer 实例,只需将其存储在值中即可。您的解决方案似乎也是一种有效的方法。
  • @Timmos 以避免另一个包装对象,您可以将 Payload 子类化并添加对密钥的引用。
  • 更喜欢组合而不是继承 :) 另外你会很幸运 ReentrantLock 不是最终类。事实上,这将是同样的事情,子类化也是一个新的类。但无论如何,这是一个题外话的讨论。感谢您的意见。
猜你喜欢
  • 2011-02-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-06
  • 1970-01-01
  • 2013-08-06
  • 1970-01-01
相关资源
最近更新 更多