Java 在使用局部变量之前,将引用复制到局部变量是否重要
在OpenJDK 8中研究java.util.LinkedList的代码时,我发现了以下代码。代码很简单,但我对在第二行代码中将对第一个节点的引用保存为常量感到困惑。据我所知,这段代码将内联到一个行程序中,而无需复制引用。我说得对吗?如果是这样,为什么在这种和类似的情况下需要复制引用(这种习惯用法可以在java.util.LinkedList中的一半方法中找到)Java 在使用局部变量之前,将引用复制到局部变量是否重要,java,Java,在OpenJDK 8中研究java.util.LinkedList的代码时,我发现了以下代码。代码很简单,但我对在第二行代码中将对第一个节点的引用保存为常量感到困惑。据我所知,这段代码将内联到一个行程序中,而无需复制引用。我说得对吗?如果是这样,为什么在这种和类似的情况下需要复制引用(这种习惯用法可以在java.util.LinkedList中的一半方法中找到) publicepeek(){ 最终节点f=第一个节点; 返回(f==null)?null:f.项; } 我的第一个想法是,它在某种程
publicepeek(){
最终节点f=第一个节点;
返回(f==null)?null:f.项;
}
我的第一个想法是,它在某种程度上有助于并发,但LinkedList不允许并发访问(除非您自己承担风险),所以我想这是对优化器的一些提示,但无法理解它应该如何工作
我的第一个想法是它在某种程度上有助于并发性
…所以我想这是对优化器的一些提示
两者都有。:-)LinkedList
不支持并发的事实并不意味着作者无论如何都不会遵循良好的实践,它告诉编译器和JIT他们只应该首先查找
如果没有f
局部变量,我们将有:
public E peek() {
return (this.first == null) ? null : this.first.item;
}
我添加了隐含的this.
以强调first
是一个实例字段
因此,如果在线程A上对this.first==null
部分求值,那么this.first
在线程B上更改,当this.first.item
在线程A上求值时,它可能会抛出,因为this.first
同时变为null
。对于f
,这是不可能的,因为f
是本地的;只有运行peek
调用的线程才会看到它
final
部分在代码文档中很好(因为作者从未打算更改f
的值),并且向优化器提示我们永远不会更改f
,这意味着当需要进行优化时,它可以将其优化到其寿命的一英寸以内,知道它只需要读取这个。首先
一次,然后可以使用寄存器或堆栈值进行null检查和返回。AFAIK,使用堆栈上的引用要比使用堆上的引用快,或者至少过去是这样。因此建议这样做,尤其是在方法中多次使用引用时。我怀疑这在今天会有很大的不同,因为JIT可能会优化一行的。可能的重复。这是一个微观优化。可能的情况是,如果在执行此方法的过程中,first
可能会发生变化,他们希望确保所有测试都在同一个引用上完成。您不想先测试是否为非null,然后让它从您的下方更改,然后在它可能已更改为null时尝试取消引用它。他们使用final来告诉编译器一旦指定,就不要更改它。基本上是说1)将对象放在第一个节点的前面。2) 若要避免在f为null时使用null指针,请返回null,否则返回节点f内的“项”。因此,实际上,这只是分配它们并在第一个节点内返回项目的问题。顺便说一句,如果节点为null,则返回null,这样就不会出现null指针,并且(tl;tr:这是一种性能优化的极端形式,并带来了一种(非常)有限的“线程安全性”)IIRCfinal
局部变量仅存在于Java源代码中,而不存在于字节码中。因此优化器不会看到它(这没有问题,因为它可以轻松确定变量是否被重新分配)。@maaartinus:在字节码级别,局部变量存在;如果您查看LinkedList#peek
的字节码,您将看到的getfield
首先是,然后是astore\u 1
,然后该方法的所有其余部分都对该局部变量1进行操作。JIT可能更进一步,但最初变量是存在的。所以,你是说OP建议JIT将f内联到这个。f是错误的。我发现我的公式太草率了。我的意思是:局部变量的final
修饰符不存在。省略修饰符可能会导致完全相同的字节码(AFAIK javac可以自由进行优化,其结果可能不同)。@maaartinus这是正确的<局部变量的code>final
仅存在于javac
中,不会插入任何障碍(与通过构造函数设置的final
相反)
public E peek() {
return (this.first == null) ? null : this.first.item;
}