使用前的Java Lock变量赋值。为什么?

时间:2011-11-05 11:18:03

标签: java multithreading concurrency thread-safety

在很多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所以我很确定它是有原因的。请不要指出代码只是等效的。

由于

5 个答案:

答案 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 人使用的特定代码中,性能确实非常重要。

相关问题