假设我们有一个类Container
class Container {
private final Map<LongHolder, Integer> map = new ConcurrentHashMap<>();
static class LongHolder {
private final Long i;
private LongHolder(Long i) {
this.i = i;
}
}
public LongHolder createValue(Long i) {
LongHolder v = new LongHolder(i);
map.put(v, 1)
return LongHolder;
}
public void doSmth(LongHolder longHolder) {
map.get(longHolder);
... // do smth
}
}
是否可以以这样的方式对操作进行重新排序,以使从createValue引用LongHolder的方法转义该方法,从而在将其他线程放入映射之前变为可用的其他线程?在这种情况下,我们可以获取LongHolder引用形式createValue并将其传递到在地图中看不到的其他线程中的doSmth。可能吗?如果没有,请解释。
更新:
ConcurrentHashMap的Javadoc指出,检索反映了保留在其上的最新完成的更新操作的结果
发作。 (更正式地说,给定密钥的更新操作带有一个
与以下任何(非空)检索相关的事件发生之前
该键报告更新后的值。)
但是,最近完成的更新操作包括
问题恰恰是关于重新排序时的这种微妙情况
可能以实际更新操作的方式发生
尚不完整,引用在
put
之前转义。ConcurrentHashMap
仅说明给定密钥带有一个事实与该键的任何(非空)检索之前发生的关系
换句话说,只有当我们检索到该键时,我们才能辩论
有关此键的更新操作之前发生的问题。
我只能提出其他一些无法实现的原因:
1)重新排序规则比我想象的要严格,我们不能假定仲裁重新排序(或者可以吗?规则是什么?)。
2)
put
具有一些不允许重新编排的魔术属性,并且return
仅在完成put
之后才会发生。在那种情况下,这些属性是什么? 最佳答案
假设线程A
和B
引用了Container
实例。
(由于Container.map
被声明为final
,其值将被安全地发布。此外,由于它引用了ConcurrentHashMap
,因此不需要进行同步就可以保持线程安全。)
现在假设线程A
调用Longholder holder = container.createValue(x)
,并以某种方式将holder
传递给线程B
。您的问题是,如果B
调用doSmth
传递了该所有者,那么map.get(holder)
调用会在地图上看到它吗?
答案是肯定的。ConcurrentHashMap
的规范说:
“检索反映了自启动以来最新完成的更新操作的结果。(更正式的说,给定键的更新操作与该键的任何(非空)检索报告更新后的值具有事前发生的关系。 )”。
这意味着在put
调用之前发生了线程A
在线程get
中随后发生的B
调用之前发生的情况。这又意味着:get
将找到LongHolder
密钥,然后get
将返回正确的值。LongHolder
对象的i
字段的值也将与预期的一样,即使LongHolder
是可变持有者也是如此。
(请注意,在这里声明不可变的LongHolder
没有什么意义。尽管具有更简单的API,它等效于java.lang.Long
...。)
是否可以以这样的方式对操作进行重新排序,以使从LongHolder
引用createValue
的方法转义该方法,从而在将其放入映射之前可用于其他线程?
基本上没有相关操作如下:
LongHolder v = new LongHolder(i);
map.put(v, 1)
return v; // corrected typo
没有任何意义的源代码重新排序。
如果您要谈论的是编译器针对运行
createValue
的线程进行的重新排序,那么JLS不允许这样做。禁止更改线程内可见性的任何重新排序。由于
v
和get
的属性,通过地图发布put
是安全的发布。 (在实现中,存在内存障碍,禁止在get
和put
调用周围进行有害的重新排序。)3.是关系发生之前发生的结果。
现在,如果代码要更改为此:
LongHolder v = new LongHolder(i);
map.put(v, 1)
// Somehow modify v.i
return v;
那将是不安全出版的一种形式。由于
v.i
对A
的更改发生在put
之后,因此put
和get
中的B
之间的关系发生在发生之前,不足以保证获得新的v.i
值。在线程B
中可见。那是因为链接不再起作用。现在,我想如果线程
createValue
中A
调用的结果以不安全的方式传递给另一个线程(B
或C
),则不能保证后者看到正确的值。 v.i
...如果LongHolder
是可变的。但这不是createValue
/ doSmth
代码的问题。并且通过地图发布v
的值是安全的。但是我认为关于重新排序的讨论没有抓住重点。禁止任何违反内存模型所保证的可见性语义的重新排序。 JIT编译器不允许这样做。因此,您只需要在分析之前进行操作即可。