将实例字段分配给局部变量

16

这是JDK中HashMap类的keySet()方法。为什么作者将keySet字段分配给局部变量ks

public Set<K> keySet() {
    Set<K> ks;
    return (ks = keySet) == null ? (keySet = new KeySet()) : ks;
}
上面和下面有什么区别?这与线程安全有关吗?
public Set<K> keySet() {
    return (keySet == null ? (keySet = new KeySet()) : keySet;
}

也许吧;不过看起来他正在尝试设置一个类级别的变量,同时确保该变量不为空并返回该值。除此之外,我一无所知。我需要查看整个类。 - Richard Barker
或许是为了意图的清晰性 - Sajeev
1
可能被视为以下问题的重复:https://dev59.com/14jca4cB1Zd3GeqPzJ7t,https://dev59.com/I3E85IYBdhLWcg3waC1T,https://dev59.com/SFoU5IYBdhLWcg3wI0hM,https://dev59.com/b4_ea4cB1Zd3GeqPKzDC,以及其他一些类似的问题... - Marco13
2个回答

9

如果您查看抽象类AbstractMap<K,V>中的keySet声明,您会发现它定义如下:

transient volatile Set<K>  keySet;

由于它是易失性的,使用本地变量赋值只读取一次比在您提供的另一个示例中读取两次更加便宜。

此外,如果直接返回keySet变量,则所有客户端代码都将处理易失引用与非易失引用(即Set<K> ks


2
谢谢你关于volatile字段读取的回答,我没有想到过。你能否解释一下“处理volatile引用与非volatile引用”的部分? - joohwan
1
不存在“易失性引用与非易失性引用”的概念。 - Boann

8
为了对迈克尔的回答进行稍微的拓展,我认为这里的目的是确保keySet()方法永远不会返回null,可能还能提供性能优势。
鉴于此代码:
public Set<K> keySet() {
    return (keySet == null ? (keySet = new KeySet()) : keySet;
}

在多线程代码中,理论上至少有可能在第一次读取(keySet == null)和返回之间将keySet字段设置为null。我没有查看其余的代码,但我认为还有其他地方可能会将keySet分配为null。这是因为在野外发现的问题还是一种防御措施,这是作者需要回答的问题。
实际代码:
public Set<K> keySet() {
    Set<K> ks;
    return (ks = keySet) == null ? (keySet = new KeySet()) : ks;
}

由于该字段仅被读取一次,因此不会存在这个问题。


是的,这与在多线程环境中保持安全有关。 - Andrew Eisenberg
@msandiford 这并不一定是在某个地方设置为 null,操作可能只是重新排序了,因为共享变量有两个竞争读取。 - Eugene

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接