【问题标题】:Referencing class variables from methods using extra local variables使用额外的局部变量从方法中引用类变量
【发布时间】:2019-02-01 14:35:14
【问题描述】:

另一天,我在阅读 Doug Lea 的 ArrayBlockingQueue 实现代码,注意到很多方法 (public, default, and private) 有以下引用:

final Object[] items = this.items;
final ReentrantLock lock = this.lock;

我已经四处询问了一个合理的解释,但到目前为止还没有令人满意的答案。我不太清楚为什么我们首先需要有这样的局部变量?以这种方式编码有什么好处?

也许我错过了并发编程中的一些重要点。你能帮忙解释一下吗?

【问题讨论】:

  • 欢迎来到 Stack Overflow!请使用tour(您将获得徽章!),环顾四周,并通读help center,尤其是How do I ask a good question? 我还推荐Jon Skeet 的Writing the Perfect Question 它会帮助人们帮助你a很多如果您向我们展示了您所询问的示例。
  • 对于我们这些不熟悉Doug Lee的人,您能否给出他的示例代码的链接,或者在此处粘贴他的一些示例代码。
  • @tgolisch 我用一个指向具有引用类代码的 OpenJDK 镜像的链接编辑了问题。
  • @tgolisch 对不起。我拼错了他的名字:Doug Lea,他是标准 Java 库的主要作者,特别是多线程和并发包的作者。我提到的类是Java5包“java.util.concurrent”上的核心java类之一。感谢 Jiri 添加源代码链接。
  • 我的猜测是这些变量是易失性的,将它们存储在局部变量中意味着您只需执行一次易失性读取,而不是每次使用该变量时。我认为这就是 John Bollinger 以更详细的技术方式解释的内容。

标签: java


【解决方案1】:

方法将局部变量设置为可访问的类或实例变量的值,或通过其中之一可访问的值的一个很好的理由是此后独立于任何修改其他线程到该变量。需要注意的是,这允许需要多次访问该值的方法执行一致的计算,以反映某个特定时间点的宿主对象的状态,即使在返回结果时该状态已更改。这很可能是您正在研究的代码中发生的情况。

【讨论】:

  • 约翰,非常感谢您的解释。我有点理解你在说什么。但是我还是有点困惑,因为引用的类变量是最终的,不能重新评估,例如: final Object[] items = this.items;上面的局部变量“items”只是一个指向“this.items”的指针,对“this.items”的任何修改都由另一个局部变量“lock”保护,它再次引用“this.lock”。 final ReentrantLock lock = this.lock;
  • 显然不止一个线程可以同时访问“this.items”,但一次只有一个线程可以对 Object[] 项进行更改。您可以直接对“this.items”执行相同的操作,没有任何问题(我猜),或者不是?为什么这里又是一个本地的“最终 ReentrantLock 锁”?这种额外的间接性给我们带来了什么好处?您能否详细说明一下,以便我更好地理解?非常感谢。
  • @victoryao,事实上,如果不分析代码,我无法详细说明。但重点不是防止this.items元素 发生更改,而是防止this.items 本身被对不同数组的引用替换。那么,假设这实际上可能发生,并进一步假设this.lock 应该保护对该数组成员的访问,那么当 items 数组发生时,锁被替换是完全合理的。那将是同一总体计划的另一部分,它将解释锁定参考的复制。
  • 是的,如果this.items 可以替换为对不同数组的引用,我同意你的看法; this.lock 也是如此。两者都被声明为最终类变量:final Object[] items; final ReentrantLock lock。我相信最终的变量是不可重新分配的。或者我误解了你关于防止 this.items 本身被替换为对不同数组的引用
  • 当然假设在一种方法中您可以执行以下操作:Object[] items = this.items; items = new Object[10];。但是,对items 的重新分配不会影响this.items。还是我弄错了?如果不是这种情况或可以替换最终变量的其他方式,请纠正我?无论如何,这是一个有趣的观点。谢谢约翰。
【解决方案2】:

碰巧我刚刚看到这个链接,它解释了以这种方式编码的一些主要论点:[In ArrayBlockingQueue, why copy final member field into local final variable?.请阅读它以了解更多信息,相反,我希望不要变得更加困惑。我相信它可以帮助您从另一个角度看待这种做法。它似乎至少满足了我对这种编码风格的一些好奇心。

【讨论】:

  • 还有一个地方在谈论为什么使用final ReentrantLock lock = this.lock;。 [stackoverflow.com/questions/8089920/…请看一下以了解更多有关该主题的信息。
  • 感谢您的回答,这为我解决了问题。我认为这回答了你的问题,不是吗?您可以接受自己的答案。
  • @NickL 确实如此。我添加了一个摘要作为我对这个问题的最终答案。感谢大家对这个主题感兴趣。
【解决方案3】:

在完成将最终类变量分配给本地副本的编码实践的所有相关线程之后,即永远不会从方法内直接访问最终类变量,而是始终通过局部变量引用来引用它:

final Object[] items = this.items;

final ReentrantLock lock = this.lock;

通常你会在ArrayBlockingQueue 和其他并发类中找到这种代码风格

以下是我的发现:

  • 这是一种惯用用法,由 Doug Lea 流行,他是 多线程/并发类的核心 Java 库
  • 这种编码实践(或者更确切地说是一种 hack)的主要考虑是针对小 Java 5 时代的性能优化
  • 这种技巧是否能带来性能提升是有争议的;有人认为它是相反的 使用现代编译器;其他人认为不需要

所以我认为不应该鼓励我们采用这种做法。因为在许多应用程序中您不需要它。干净的代码可能比小的性能提升更重要;更不用说没有人 100% 确定这(性能提升)是否已经存在了。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-03-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-29
    相关资源
    最近更新 更多