JavaDoc for ConcurrentHashMap
states:
检索操作(包括get)一般不会阻塞,因此可能与更新操作重叠(包括put和remove)。检索反映了最近完成的更新操作的结果。 (更正式地说,给定密钥的更新操作与报告更新值的该密钥的任何(非空)检索之前发生关系。)
因为"给定密钥的更新操作...在...之前发生...对该密钥的任何(非空)检索"并且部分构造的对象被视为非null,*以下代码是否可能允许Thread2
访问部分构造的对象?
Thread1
// Immutable Object (all fields final)
concurrentHashMap.put("immutableObject", new ImmutableObject());
// Volatile Object (all fields volatile)
concurrentHashMap.put("volatileObject", new VolatileObject());
// Thread-safe Mutable Object
concurrentHashMap.put("mutableObject", new MutableObject());
Thread2
concurrentHashMap.get("immutableObject");
concurrentHashMap.get("volatileObject");
concurrentHashMap.get("mutableObject");
是否需要在这些对象的构造函数中执行某种同步,以确保在完全初始化之前没有线程访问它们?
*我并非100%确定部分构建的对象被认为是非空的,但我还没有看到任何相反的证据。似乎因为线程可以访问部分初始化的对象,所以部分初始化的对象不能为空,因为可以访问一些内部数据(无论是初始化的)。
答案 0 :(得分:6)
在您的示例中,您永远不会将部分构造的对象添加到地图中
在传递给方法之前评估参数。
15.12.4. Run-Time Evaluation of Method Invocation JLS确实表明参数表达式(第二步)在方法执行之前进行评估(最后一步):
在运行时,方法调用需要五个步骤。首先是目标 可以计算参考。 其次,参数表达式是 评价即可。第三,要调用的方法的可访问性是 检查。第四,要执行的方法的实际代码是 位于。 第五,创建一个新的激活帧,同步是 必要时执行,并将控制转移到方法代码。
所以这里:
concurrentHashMap.put("immutableObject", new ImmutableObject());
评估 new ImmutableObject()
并将ImmutableObject
对象完全构建,然后将其传递给concurrentHashMap.put(..)
方法。
答案 1 :(得分:1)
put
的调用将逐个阻塞,直到每个构造函数完成,从而使get
操作成为原子。
因此,如果put
调用不是null
调用的顺序调用,他们可以在键值对已经存在的情况下查询地图,也可以不生成Map<String, Foo> map = new ConcurrentHashMap<>();
map.put("", new Foo());
class Foo {
Foo() {
while (true) {}
}
}
。
这是一个将永远运行的最小例子,用于说明:
<MyControl>
<Grid>
<Grid.ColumnDefinitions>...</Grid.ColumnDefinitions>
<ToggleButton />
<ToggleButton />
<ToggleButton />
...
<ToggleButton />
</Grid>
</MyControl>
答案 2 :(得分:0)
concurrentHashMap.put("immutableObject", new ImmutableObject());
这一行按以下顺序执行两项操作:
所以答案是否定的,hashmap不能返回部分构造的对象,因为在tehy被添加到hashmap之前完全构造了对象。