为什么在HashMap.keySet()中声明局部变量ks? [英] Why is the local variable ks declared in the HashMap.keySet()?

查看:53
本文介绍了为什么在HashMap.keySet()中声明局部变量ks?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我查看了源代码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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆