Java WeakHashMap与并发修改

Java WeakHashMap与并发修改,java,multithreading,concurrency,garbage-collection,Java,Multithreading,Concurrency,Garbage Collection,我正在阅读Java文档,了解了基本概念。 由于GC线程在后台工作,您可能会获得“异常行为”,例如在迭代时出现ConcurrentModificationException等 我不明白的是,如果默认实现没有同步,并且没有以任何方式包含锁,那么为什么不可能得到不一致的状态呢。 假设你有两个线程。GC线程在某个索引处删除某个键,同时,在同一索引处,用户线程在数组中插入一个键值对 对我来说,如果没有同步,那么获取不一致的哈希映射的风险很高 更糟糕的是,这样做实际上可能非常危险,因为v实际上可能为null

我正在阅读Java文档,了解了基本概念。 由于GC线程在后台工作,您可能会获得“异常行为”,例如在迭代时出现ConcurrentModificationException等

我不明白的是,如果默认实现没有同步,并且没有以任何方式包含锁,那么为什么不可能得到不一致的状态呢。 假设你有两个线程。GC线程在某个索引处删除某个键,同时,在同一索引处,用户线程在数组中插入一个键值对

对我来说,如果没有同步,那么获取不一致的哈希映射的风险很高

更糟糕的是,这样做实际上可能非常危险,因为v实际上可能为null

if (map.contains(k)) {
   V v = map.get(k)
}

我遗漏了什么吗?

您所描述的是文档中明确指出的内容:

因为垃圾收集器可以随时丢弃密钥,
WeakHashMap
的行为可能就好像未知线程正在静默地删除条目一样

您所犯的唯一错误是假设您可以通过同步来保护状态。这不起作用,因为同步在GC方面不是相互的。要引用文档,请执行以下操作:

特别是,即使您在
WeakHashMap
实例上同步,并且没有调用其任何mutator方法,
size
方法也可能随时间返回较小的值,
isEmpty
方法返回
false
,然后返回
true
,对于
containsKey
方法返回给定键的
true
和以后的
false
,对于
get
方法返回给定键的值,但以后返回
null
,对于
put
方法返回
null
remove
方法返回
false
,对于之前出现在映射中的键,对于键集、值集合和条目集的连续检查,以产生连续较小的元素数

此类主要用于关键对象,其equals方法使用==运算符测试对象标识。一旦这样的密钥被丢弃,它就永远无法重新创建,因此不可能在以后的某个时间在WeakHashMap中查找该密钥,并且会惊讶地发现其条目已被删除

因此,如果对基于身份检查的
equals()
对象使用
WeakHashMap
,则一切正常。您提到的第一种情况(“GC线程在某个索引处删除某个键,同时在同一索引处,用户线程正在数组中插入一个键值对。”)是不可能的,因为只要用户线程保持对键对象的引用,GC就不能丢弃它

第二个例子也是如此:

if (map.contains(k)) {
   V v = map.get(k)
}
您保留引用
k
,因此相应的对象是可访问的,不能丢弃

但是

该类可以很好地处理其 方法不基于对象标识,例如字符串实例。 但是,对于此类可重新创建的关键点对象,自动删除 密钥已被丢弃的WeakHashMap条目可能被证明是无效的 令人困惑

即使在WeakHashMap[…]上进行同步,size方法也可能随时间返回较小的值

javadoc向我充分解释了存在不一致状态的可能性,并且它完全独立于同步

在后面的几个例子中,也提到了给定的例子:

对于给定的键,containsKey方法返回true,然后返回false

因此,基本上,我们不应该依赖于
WeakHashMap
的状态。但是尽可能地使用原子。因此,应将给出的示例改写为

V v = map.get(k);
if(null != v) {
}


您提到的不一致状态问题不会出现,因为GC不会主动重组WeakHashMaps。当垃圾收集器释放弱引用的referent时,相应的条目不会从映射中物理删除;条目只是变得陈旧,没有键。在稍后的某个时间点,该条目可能会在地图上的其他操作中被物理删除,但GC不会承担该责任


您可以在上看到此设计的一个Java版本的实现。

我认为问题在于,当GC线程删除“k1”键并且当前线程同时获取“k1”值时,如何确保我们不会进入不一致状态,所有这些都没有同步。在什么意义上不一致?例如,在GC将“k1”从映射中删除后,仍然获取其值。为什么会发生这种情况?不一致的意义是,如果映射中存在冲突,GC将收回k1,用户插入k2,两个键都冲突,那么如果没有同步,k2很可能永远不会被插入,或者被插入但无法访问
k
不需要与
put
方法中用作键的引用相同。然后
map.contains(k)
将返回
false
,所有都可以:)对于第一种情况,如果哈希映射中存在冲突,然后GC可能试图删除一个键k1,用户插入的一个键k2都发生在同一个索引中。我们可以有两个键
k1
k2
,它们是
k1!=k2
仍然可以为
map生成
true
。contains(k1)
map.contains(k2)
之后只
map.put(k1,v)
@tsolakp啊,我的错,我应该补充一点,我的答案只适用于
equals()
基于身份检查的对象。编辑了答案,谢谢you@tsolakp:执行触发remo的操作的线程
Optional.ofNullable(map.get(k)).ifPresent(() -> { } );