【问题标题】:Concurrent map with weak keys带有弱键的并发映射
【发布时间】:2013-06-28 10:46:53
【问题描述】:

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

每个资源都可以通过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:我的第一个猜测是我应该从地图迁移到缓存。这可能是最好的解决方案吗?我以前从未使用过 Guava,但现在我发现缓存的键比较有同样的限制。

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

【问题讨论】:

    标签: java multithreading collections concurrency guava


    【解决方案1】:

    你需要弱引用键和值。

    我建议你切换到缓存,或者除非你切换到ConcurrentMapSoftReferences - 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 之类的方法。

    【讨论】:

    • 但是这个实现会从映射中删除实际存储在其中的Entry 对象吗?我认为它只会删除条目的值并保持地图受到污染。
    • @raphw 您有两个选择:通过 getcontains 方法懒惰地删除旧条目(示例请参阅我的 get 方法),或者通过 @987654323 急切地删除旧条目@
    【解决方案2】:

    我找到了另一个我实际实施的巧妙解决方案。也许一开始就想起来太容易了:

    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。这就像一个魅力。键不再代表对用作监视器的实际对象的强引用,并且只要不再有线程持有对它们的强引用,映射的条目就会被垃圾收集。

    【讨论】:

    • 嗨,do {..,} while(monitor == null) 循环的意义何在?您是否试图保护您的 create-read-check 三重操作免受其他线程或 GC 会删除条目的竞争条件?在这种情况下,读取监视器、映射get() 和在while() 中检查它仍然容易出现竞争条件。您要么必须同步整个 aquireMonitor(),要么使用 Map 的 computeIfAbsent(),或者只使用 compute()(从 Java 8 开始)。
    • 所以您认为 Guava (??) new MapBuilder().weakValues().makeMap() 和 Java 标准 new ConcurrentHashMap&lt;Object, WeakReference&lt;Object&gt;&gt; 之间的主要区别在于前者清除所有条目的映射,即键和值,如果值较弱GC 清除了引用?除非手动清除,否则后者会保留条目。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多