ConcurrentHashMap 是否会通过 get() 公开部分构造的对象?
Will a ConcurrentHashMap ever expose a partially constructed object via get()?
JavaDoc for ConcurrentHashMap
states:
Retrieval operations (including get) generally do not block, so may overlap with update operations (including put and remove). Retrievals reflect the results of the most recently completed update operations holding upon their onset. (More formally, an update operation for a given key bears a happens-before relation with any (non-null) retrieval for that key reporting the updated value.)
由于 "an update operation for a given key... happens-before... any (non-null) retrieval for that key" 并且部分构造的对象被认为是非空的,*以下代码是否可能允许 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% 确定部分构造的对象被认为是非空的,但我没有看到任何相反的证据。似乎因为线程可以访问部分初始化的对象,所以部分初始化的对象不能为空,因为一些内部数据(无论初始化的是什么)是可以访问的。
concurrentHashMap.put("immutableObject", new ImmutableObject());
这一行按以下顺序做了两件事:
- 创建一个新的 ImmutableObject
- 将创建的对象添加到hashmap
所以答案是否定的,hashmap 不能 return 部分构造的对象,因为对象甚至在添加到 hashmap 之前就已经完全构造了。
put
的调用将一直阻塞,直到每个构造函数完成,从而使 put
操作成为原子操作。
因此,如果 get
调用与 put
调用不连续,它们可能会在键值对已经存在时查询地图,或者不返回 null
.
这是一个将永远 运行 的最小示例,用于说明:
Map<String, Foo> map = new ConcurrentHashMap<>();
map.put("", new Foo());
class Foo {
Foo() {
while (true) {}
}
}
在您的示例中,您永远不会将部分构建的对象添加到地图中。
参数在传递给方法之前进行评估。
15.12.4. Run-Time Evaluation of Method Invocation JLS 确实指出参数表达式(第二步)在 方法执行(最后一步)之前 被评估:
At run time, method invocation requires five steps. First, a target
reference may be computed. Second, the argument expressions are
evaluated. Third, the accessibility of the method to be invoked is
checked. Fourth, the actual code for the method to be executed is
located. Fifth, a new activation frame is created, synchronization is
performed if necessary, and control is transferred to the method code.
所以在这里 :
concurrentHashMap.put("immutableObject", new ImmutableObject());
new ImmutableObject()
被评估并且 ImmutableObject
对象在被传递给 concurrentHashMap.put(..)
方法之前完全构建。
JavaDoc for ConcurrentHashMap
states:
Retrieval operations (including get) generally do not block, so may overlap with update operations (including put and remove). Retrievals reflect the results of the most recently completed update operations holding upon their onset. (More formally, an update operation for a given key bears a happens-before relation with any (non-null) retrieval for that key reporting the updated value.)
由于 "an update operation for a given key... happens-before... any (non-null) retrieval for that key" 并且部分构造的对象被认为是非空的,*以下代码是否可能允许 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% 确定部分构造的对象被认为是非空的,但我没有看到任何相反的证据。似乎因为线程可以访问部分初始化的对象,所以部分初始化的对象不能为空,因为一些内部数据(无论初始化的是什么)是可以访问的。
concurrentHashMap.put("immutableObject", new ImmutableObject());
这一行按以下顺序做了两件事:
- 创建一个新的 ImmutableObject
- 将创建的对象添加到hashmap
所以答案是否定的,hashmap 不能 return 部分构造的对象,因为对象甚至在添加到 hashmap 之前就已经完全构造了。
put
的调用将一直阻塞,直到每个构造函数完成,从而使 put
操作成为原子操作。
因此,如果 get
调用与 put
调用不连续,它们可能会在键值对已经存在时查询地图,或者不返回 null
.
这是一个将永远 运行 的最小示例,用于说明:
Map<String, Foo> map = new ConcurrentHashMap<>();
map.put("", new Foo());
class Foo {
Foo() {
while (true) {}
}
}
在您的示例中,您永远不会将部分构建的对象添加到地图中。
参数在传递给方法之前进行评估。
15.12.4. Run-Time Evaluation of Method Invocation JLS 确实指出参数表达式(第二步)在 方法执行(最后一步)之前 被评估:
At run time, method invocation requires five steps. First, a target reference may be computed. Second, the argument expressions are evaluated. Third, the accessibility of the method to be invoked is checked. Fourth, the actual code for the method to be executed is located. Fifth, a new activation frame is created, synchronization is performed if necessary, and control is transferred to the method code.
所以在这里 :
concurrentHashMap.put("immutableObject", new ImmutableObject());
new ImmutableObject()
被评估并且 ImmutableObject
对象在被传递给 concurrentHashMap.put(..)
方法之前完全构建。