在ArrayBlockingQueue中,为什么将最终成员字段复制到本地最终变量中?


80

在中ArrayBlockingQueue,所有需要锁定的方法都将final在调用之前将其复制到局部变量lock()

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.lock到一个局部变量lock时,现场this.lockfinal

此外,E[]在执行操作之前,它还会使用的本地副本:

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

有什么理由将最终字段复制到本地最终变量?

Answers:


66

该类的作者Doug Lea喜欢使用这种极端的优化方法。这是有关core-libs-dev邮件列表的最新主题的帖子,有关这个确切的主题,可以很好地回答您的问题。

从帖子中:

...复制到本地会产生最小的字节码,对于低级代码,最好编写离机器更近的代码


15
强烈强调“极端”!这不是每个人都应该模仿的通用的良好编程习惯。
凯文·布罗里恩

15
随机FYI:在某些其他情况下,您看到此操作已完成,这是因为所讨论的字段是易变的,并且该方法需要确保它始终具有单个一致的值或参考。
凯文·布劳里恩

2
我将在这样的核心类中进行这种“极端”优化。
埃里克·罗伯逊

4
@zamza,局部最终变量仅由Java编译器使用,而不是字节码(即JVM不知道局部变量是否为最终变量)
bestsss

1
除了字节码大小之外,这是否还优化了执行速度?
SantiBailors

11

该线程给出了一些答案。本质上:

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

2
我认为finalJVM不必重新加载变量。如果final通过反射修改变量,则无法保证程序正常运行(这意味着可能不会在所有情况下都考虑新值)。
icza
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.