为什么在HashMap.keySet()中声明局部变量ks? [英] Why is the local variable ks declared in the HashMap.keySet()?
问题描述
我查看了源代码java.util.HashMap,并看到以下代码:
I looked at the source code java.util.HashMap and saw the following code:
public Set<K> keySet() {
Set<K> ks;
return (ks = keySet) == null ? (keySet = new KeySet()) : ks;
}
(Windows,Java版本为"1.8.0_111")
在我的MacBook上,它看起来像这样:
On my MacBook it looks like this:
public Set<K> keySet() {
Set<K> ks = keySet;
if (ks == null) {
ks = new KeySet();
keySet = ks;
}
return ks;
}
(MacOs X Sierra,Java版本"1.8.0_121")
为什么两个变体都声明局部变量ks?为什么它不是这样写的:
Why do both variants declare a local variable ks? Why is it not written like this:
public Set<K> keySet() {
if (keySet == null) {
keySet = new KeySet();
}
return keySet;
}
或
public Set<K> keySet() {
return keySet == null ? (keySet = new KeySet()) : keySet;
}
推荐答案
据我所知,这是一个非常简洁的优化.
As far as I can tell this is an optimization that is pretty neat.
以前是这样写的:
if (keySet == null) { // volatile read
keySet = new AbstractSet<K>() { // volatile write
....
return keySet; // volatile read
这些操作无法重新排序,因为这里插入了内存屏障.所以看起来像这样:
These operations can not be re-ordered because there are memory barriers that are inserted here. So it would look like this:
[StoreLoad]
// volatile read
[LoadLoad]
[LoadStore]
[StoreStore]
[LoadStore]
// volatile write
[StoreLoad]
[StoreLoad] // there's probably just one barrier here instead of two
// volatile read
[LoadLoad]
[LoadStore]
这里有很多障碍,最昂贵的是在 x86
上发出的 StoreLoad
.
There are lots of barriers here and the most expensive would be the StoreLoad
that is emitted on x86
.
假设我们在此处放置 volatile
.由于没有插入障碍,因此可以按照自己喜欢的任何方式对这些操作进行重新排序,并且 keySet
变量在此处有两次读取.
Suppose we drop the volatile
here. Since there are no barriers inserted these operations can be re-ordered in any way they please and there are two racy reads here of the keySet
variable.
我们可以读取单个变量并将变量存储到本地字段中(因为它们是本地的,所以它们是线程安全的-没有人可以更改在本地声明的引用),据我所知,这是唯一的问题是多个线程可能同时看到一个空引用,并使用空的 KeySet
对其进行初始化,并且可能会执行过多的工作;但这很可能比壁垒便宜.
We can have a single racy read and store the variable into a local field(since they are local, they are thread safe - no one can change the reference that is locally declared), the only problem as far as I can see is that multiple threads might see a null reference at the same time and initialize it with an empty KeySet
and potentially doing too much work; but that is most probably cheaper than the barriers.
另一方面,如果某些线程看到一个非null引用,它将100%看到一个完全初始化的对象,这是有关 final
字段的注释.如果所有对象都是最终对象,则JMM保证在构造方法之后执行冻结"操作;或者用简单的话(IMO),如果所有字段都是最终字段并在构造函数中初始化,则在其后插入两个障碍: LoadStore
和 LoadLoad
;从而达到相同的效果.
On the other hand if some threads sees a non-null reference, it will 100% see a fully initialized object and that is the comment about final
fields. If all objects are final the JMM guarantees a "freeze" action after the constructor; or in simpler words (IMO) if all fields are final and initialized in the constructor there are two barriers inserted after it: LoadStore
and LoadLoad
; thus achieving the same effect.
这篇关于为什么在HashMap.keySet()中声明局部变量ks?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!