WeakHashMap迭代和垃圾收集

我正在使用WeaekHashMap来实现Cache。 我想知道我是否在迭代这个映射的键,同时垃圾收集器正在主动从这个映射中删除键,我会收到一个ConcurrentModificationException吗? 我不这么认为,因为据我所知,并发修改是因为应用程序代码中的错误导致开发人员忘记了解其他线程共享/使用相同的映射,在这种情况下,它不应该发生。 但是想知道当WeakHashMap不同步时JVM会如何处理?

正如bkail所说,当GC从WeakHashMap “删除”一个条目时,它不会导致并发修改。 实际上,GC通过对WeakReference对象(保存真实密钥)本身的硬引用来收集底层对象。 因此,不会收集地图直接引用的真实对象(引用对象),因此在您的某个线程调用此映射中的方法之前,映射不会更改。 此时,映射将检查GC中的引用队列,并查找已收集的所有键并将其从映射中删除 – 因此对映射结构的实际更改将发生在其中一个线程上。

考虑到这一点,可能有一种情况,你可能会在这样的地图中得到一个你不会在另一种地图中得到的并发修改 – 如果你放了一个已经存在的密钥或者调用一个getter方法。 但实际上,在并发应用程序中,您应该锁定这些调用,这样您的程序中就会出现真正的并发访问错误。

在回答你的问题时,你真的不应该使用 WeakHashMap作为缓存(即使你在谈论缓存密钥)。 在缓存中,当不再引用值时,您不希望您的值“神奇地”消失。 通常,当有一定的最大数量(类似于Apache集合LRUMap )或在内存需求上释放时,您希望它们消失。

对于后者,您可以使用带有SoftReference的映射(Apache集合提供了一个ReferenceMap ,允许您指定键或值的引用类型)。 指定软引用仅基于内存压力释放 – 另一方面弱引用必须通过GC识别出对象没有剩余硬引用并且可以随时释放它。 当然,软引用如何真正起作用还取决于JVM实现。

编辑 :我重读了你的问题,并希望解决另一个问题。 因为实际修改发生在您自己的线程上的WeakHashMap内部结构中, 如果您只在单个线程中使用此映射,则不需要同步任何方法调用 。 此行为与任何其他Map没有什么不同。

不,您不会收到ConcurrentModificationException。 当您调用各种操作时,WeakHashMap使用ReferenceQueue.poll。 换句话说,每个调用者都默默地负责清除Map中的陈旧条目。 但是,这确实意味着从多个线程调用WeakHashMap上的方法是不安全的,否则这些线程似乎是“只读”的,因为对get()的任何调用都可以破坏另一个线程试图迭代的条目链表。

WeakHashMap在键而不是值上较弱,因此如果要在未使用值时释放空间,则它不适合值的缓存。 您可能想要从google集合中查看MapMaker 。

文档在这方面并不十分清楚,但确实如此:

WeakHashMap类的行为部分取决于垃圾收集器的操作,因此几个熟悉的(但不是必需的)Map不变量不适用于此类。 因为垃圾收集器可能随时丢弃密钥,所以WeakHashMap的行为可能就像未知线程正在静默删除条目一样 。 特别是,即使您在WeakHashMap实例上进行同步并且不调用其任何mutator方法,size方法也可能随着时间的推移返回较小的值,因为isEmpty方法返回false然后返回true,以使containsKey方法返回对于给定键,true和更高版本为false,因为get方法返回给定键的值但后来返回null,put方法返回null,remove方法返回false,前面看来是在映射,以及对密钥集,值集和条目集的连续检查,以连续产生较少数量的元素。 – Java API

我认为,鉴于该描述,您应该在迭代地图时偶尔会收到ConcurrentModificationException 。 我会设计你的缓存,所以你做尽可能少的迭代。