在很多Java源代码中,(例如LinkedBlockingDeque
)我看到这样的东西;
final ReentrantLock lock = new ReentrantLock();
public void putLast(E e) throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lock();
try {
// do stuff
} finally {
lock.unlock();
}
}
我理解基本模式(锁定,最后解锁)但我的问题是为什么在使用之前对本地范围的Lock变量进行赋值?为什么要这样做而不是以下?
final ReentrantLock lock = new ReentrantLock();
public void putLast(E e) throws InterruptedException {
this.lock.lock();
try {
// do stuff
} finally {
lock.unlock();
}
}
会影响优化吗?第一个例子可以防止锁定粗化吗?
评论后编辑:如果您不知道为什么会这样,请不要添加答案。这是来自Java源码,@ author标签是Doug Lea所以我很确定它是有原因的。请不要指出代码只是等效的。
由于
答案 0 :(得分:7)
在方法中分配局部变量时,编译器可以进行一些优化。 见In ArrayBlockingQueue, why copy final member field into local final variable?
答案 1 :(得分:2)
在这段代码中,无关紧要:两个示例的工作原理完全相同。但是,如果实例变量锁不是最终的,那么它可能会产生影响,因为在锁定操作期间可以更改实例变量:然后您要确保解锁最初锁定的锁。
但是因为锁是最终的,所以没关系:第一个例子中的局部变量赋值是多余的。
答案 2 :(得分:2)
稍微挖掘一下代码我已经找到相同作者的两种方式的例子,Doug Lea:
LinkedBlockingDeque
(自JDK 1.6起)使用“直接访问”方法。CopyOnWriteArrayList
(因为JDK 1.5)使用“局部变量”方法。 java.util.concurrent
中的每个成语都有更多示例,但似乎每个类都选择了一致的样式。
请注意,在所有相关案例中,lock
字段已声明为final
。这是最重要的部分,因为最终字段的内存模型语义在JVM (see JLS)中有点特殊。
在此基础上:采用本地副本不会影响多线程的正确性。
另请注意,Dough Lea在较新的代码中选择了较短的样式(如示例所示)。因此,从java.util.concurrent
成为JDK的一部分之前以及适当采用JVM内存模型之前的日子,也许“采取本地副本”的习惯是一些遗留问题。我推测在采用之前的代码可能看起来像这样:
public void op(){
ReentrantLock lock = getLock();
lock.lock();
try {
realOp();
} finally {
lock.unlock();
}
}
其中getLock()
确实包含一些粗略的多线程安全逻辑。
答案 3 :(得分:1)
Hotspot不会优化实例最终字段。
在大多数情况下,它确实没关系,因为如果代码被编译并且它到达缓存,则优化值可能为1%,但是如果代码跨越了更多的代码,尤其是。虚拟调用本地负载可以帮助预测分支。
我,我自己,如果我知道/期望它是热门代码,大部分时间都会执行局部变量脏代码。
Some further信息,包括Doug Lea个人对此事采取了行动。
答案 4 :(得分:0)
虽然它不应该有任何区别,但在邮件列表中已经说明实际JRE的性能差异很小。
一般建议仍然不会打扰当地人。它只是存在,因为在 lot 人使用的特定代码中,性能确实非常重要。