为什么在hashmap.keyset()中声明局部变量ks



我查看了源代码java.util.hashmap并看到了以下代码:

public Set<K> keySet() {
    Set<K> ks;
    return (ks = keySet) == null ? (keySet = new KeySet()) : ks;
}

(Windows,Java版本" 1.8.0_111")

在我的MacBook上看起来像这样:

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?为什么不是这样写的:

public Set<K> keySet() {
    if (keySet == null) {
        keySet = new KeySet();
    }
    return keySet;
}

public Set<K> keySet() {
    return keySet == null ? (keySet = new KeySet()) : keySet;
}

javadoc有答案:

/**
 * Since there is no synchronization performed while accessing these fields,
 * it is expected that java.util.Map view classes using these fields have
 * no non-final fields (or any fields at all except for outer-this). Adhering
 * to this rule would make the races on these fields benign.
 *
 * It is also imperative that implementations read the field only once,
 * as in:
 *
 * public Set<K> keySet() {
 *   Set<K> ks = keySet;  // single racy read
 *   if (ks == null) {
 *     ks = new KeySet();
 *     keySet = ks;
 *   }
 *   return ks;
 * }
 *}
 */
transient Set<K> keySet;

据我所知,这是一个非常整洁的优化。

以前是这样写的:

if (keySet == null) { // volatile read
         keySet = new AbstractSet<K>() { // volatile write
          ....
return keySet; // volatile read

这些操作无法重新排序,因为这里有一些内存障碍。因此看起来像这样:

 [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

假设我们在此处删除volatile。由于没有插入这些操作的障碍,因此可以以任何方式重新排序,并且在keySet变量的这里有两次读取。

我们可以将一个简短的读数读取并将变量存储到本地字段(因为它们是本地的,它们是线程安全的 - 没有人可以更改本地声明的参考),据我所知,唯一的问题是,多个线程可能会同时看到零引用,并使用空的KeySet初始化它,并可能进行太多的工作;但这很可能比障碍便宜。

另一方面,如果某些线程看到一个非编号引用,它将100%看到一个完全初始化的对象,这是有关final字段的评论。如果所有对象都是最终的,则JMM保证在构造函数之后进行"冻结"动作;或用更简单的单词(IMO),如果所有字段均为最终并且在构造函数中初始化,则在其之后插入了两个障碍:LoadStoreLoadLoad;从而达到相同的效果。

最新更新