在 ArrayBlockingQueue 中,为什么要将 final 成员变量复制到本地 final 变量中?

86

ArrayBlockingQueue 中的所有需要锁定的方法在调用 lock() 之前,都会将锁复制到一个本地的 final 变量中。

public boolean offer(E e) {
    if (e == null) throw new NullPointerException();
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        if (count == items.length)
            return false;
        else {
            insert(e);
            return true;
        }
    } finally {
        lock.unlock();
    }
}

当字段 this.lockfinal 的时候,将其复制到本地变量 lock 是否有任何理由?

此外,在对其进行操作之前,它还使用了 E[] 的本地副本:

private E extract() {
    final E[] items = this.items;
    E x = items[takeIndex];
    items[takeIndex] = null;
    takeIndex = inc(takeIndex);
    --count;
    notFull.signal();
    return x;
}

将final字段复制到本地final变量中,是否有任何理由?

2个回答

71

这是Doug Lea,这个类的作者经常使用的一种极端优化。以下是关于此主题的最新帖子,在core-libs-dev邮件列表中的一个线程,它可以很好地回答你的问题。

来自帖子:

...将内容复制到本地变量会产生最小的字节码,对于低级别代码,编写更接近机器的代码非常方便。


16
强调“极端”!这不是每个人都应该效仿的通用良好编程实践。 - Kevin Bourrillion
15
随意提供的信息:在其他一些情况下,当你看到这样的操作时,它是因为所涉及的字段是易变的,而该方法需要确保在整个过程中它有一个单一一致的值或引用。 - Kevin Bourrillion
2
我会在像这样的核心类中进行“极致”优化。 - Erick Robertson
5
@zamza,局部最终变量仅由Java编译器使用,而不是字节码(即JVM不知道局部变量是否为final)。 - bestsss
1
据我所知,没有执行速度上的优势,但我不确定。 - ColinD
显示剩余4条评论

13

这个线程 提供了一些答案,内容如下:

  • 编译器无法轻易地证明final字段在方法内部不会改变(由于反射/序列化等)。
  • 大多数当前的编译器实际上并没有尝试,因此每次使用final字段都必须重新加载它,这可能会导致缓存未命中或页面错误。
  • 将其存储在本地变量中会强制JVM执行仅一次加载。

2
我认为final变量不需要被JVM重新加载。如果您通过反射修改final变量,则无法保证程序正确工作(意味着在某些情况下可能不考虑新值)。 - icza

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