具有弱键的并发映射

时间:2013-07-01 10:32:01

标签: java multithreading collections concurrency guava

我有一个高度并发的应用程序,它利用文件系统上的资源。两个线程可能同时访问同一资源的机会相当小,但如果发生这种情况,应用程序可能会显示有线行为。

每个资源都可以通过String坐标向量(捆绑在类ResourceIdentifier中)进行映射。在我当前的解决方案中,我创建了ConcurrentMap个此类资源标识符来收集线程在访问资源时使用的监视器:(ResourceIdentifier覆盖equalshashCode正确。)

ConcurrentMap<ResourceIdentifier, ResourceIdentifier> concurrentMap 
   = new ConcurrentHashMap<>();

public Object aquireMonitor(ResourceIdentifier resourceIdentifier) {
  concurrentMap.putIfAbsent(resourceIdentifier, resourceIdentifier);
  return concurrentMap.get(resourceIdentifier);
}

访问资源时,我同步aquireMonitor返回的监视器对象上的访问权限。据我了解ConcurrentHashMap的实现,这并不一定会阻塞所有线程(我读this blog article以了解实现。)并且我的应用程序可以愉快地运行而没有并发访问的危险在极少数情况下,以前引入丑陋漏洞的资源之一。

但是:我的应用程序管理大量资源,concurrentMap随运行时增长。这就是为什么我现在尝试将弱引用语义添加到我的应用程序中(通过使用Guava):

ConcurrentMap<ResourceIdentifier, ResourceIdentifier> concurrentMap 
   = new MapBuilder().weakValues().weakKeys()
     .concurrencyLevel(CONCURRENCY_LEVEL).makeMap();

public Object aquireMonitor(ResourceIdentifier resourceIdentifier) {
  ResourceIdentifier monitor;
  do {
    concurrentMap.putIfAbsent(resourceIdentifier, resourceIdentifier);
    monitor = concurrentMap.get(resourceIdentifier);
  } while(monitor == null);
  return monitor;
}

CONCURRENCY_LEVEL当然是静态字段。

我的想法是这样的:每当一个监视器仍然被另一个线程使用时,它当然会持有(强)对该监视器的引用。因此,ConcurrentMap中的条目不会被垃圾收集,并且可以保证在两个线程想要访问同一资源时共享监视器。 (该循环解决了putIfAbsentget的调用之间可能的垃圾收集问题。)

但是,MapMaker.weakKeys违反了equals找到条目的合同,而是使用标识。

现在我想知道:有人知道从哪里开始吗?或者这种方法反正是一个坏主意?并且作为一个附带问题:如果我只使用weakValues,是否会从地图中删除整个条目?或者地图总是会通过其键有另一个强引用?谢谢你的帮助!

PS:我的第一个猜测是我应该从地图迁移到缓存。这可能是最好的解决方案吗?我以前从未使用过番石榴,但是现在我发现对缓存的密钥比较有相同的限制。

PPS:我无法在文件系统上创建锁。 (不是我的电话。)

2 个答案:

答案 0 :(得分:4)

您需要对键和值都使用弱引用。

我建议您切换到缓存,或禁止使用SoftReferences切换到ConcurrentMap - GC非常渴望收集弱引用,因此他们并不是真的适用于缓存,但它会延迟软引用的收集,同时仍然不允许它们导致OutOfMemoryError。要实现软引用并发映射,您需要创建一个ConcurrentMap包装器,例如

class SoftConcurrentMap<K, V> extends ConcurrentHashMap<SoftReference<K>, SoftReference<V>> {
    ConcurrentHashMap<SoftReference<K>, SoftReference<V>> map = new ConcurrentHashMap<>();

    V public void get(Object key) {
        SoftReference<V> value = map.get(new SoftRefrence(key));
        if(value != null && value.get() != null) {
            return value.get();
        } else {
            map.remove(new SoftReference(key));
            return null;
        }
    }

    V put(K key, V value) {
        SoftReference<V> oldValue = map.put(new SoftReference(key), new SoftReference(value));
        return oldValue == null ? null : oldValue.get();
    }
}

等等。这是很多方法包装,因此我建议您使用类似EHCache的方法。

答案 1 :(得分:0)

我找到了另一个我实际实施的简洁解决方案。也许在开始时想到它太容易了:

ConcurrentMap<ResourceIdentifier, ResourceIdentifier> concurrentMap 
   = new MapBuilder().weakValues()
     .concurrencyLevel(CONCURRENCY_LEVEL).makeMap();

public Object aquireMonitor(ResourceIdentifier resourceIdentifier) {
  ResourceIdentifier monitor;
  do {
    concurrentMap.putIfAbsent(resourceIdentifier, new Object());
    monitor = concurrentMap.get(resourceIdentifier);
  } while(monitor == null);
  return monitor;
}

虽然没有使用weakKeys。这就像一个魅力。这些键不再代表对用作监视器的实际对象的强引用,并且只要没有线程对它们有强引用,映射的条目就会被垃圾收集。