Java中的双重检查锁定易失 [英] volatile in double-checked locking in Java

查看:88
本文介绍了Java中的双重检查锁定易失的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

据我了解,这是Java(自Java 5起)中双重检查锁定模式的正确实现:

class Foo {
    private volatile Bar _barInstance;
    public Bar getBar() {
        if (_barInstance == null) {
            synchronized(this) { // or synchronized(someLock)
                if (_barInstance == null) {
                    Bar newInstance = new Bar();
                    // possible additional initialization
                    _barInstance = newInstance;
                }
            }
        }
        return _barInstance;
    }
}

我想知道是否缺少volatile是一个严重的错误,还是只是轻微的瑕疵,假设_barInstance仅通过getBar访问.

我的想法如下:synchronized引入了 happens-before 关系.初始化_barInstance的线程将其值写入主存储器,而离开同步块.因此即使_barInstance不是volatile,也不会进行双重初始化:其他线程在其_barInstance的本地副本中具有null(在第一次检查中获取true),但必须读取进入同步块后,在第二次检查中从主存储器中获取新值(获取false并且不重新初始化).因此,唯一的问题是过多的每线程一个锁获取.

据我了解,这在CLR中是正确的,我相信在JVM中也是正确的.我说的对吗?

谢谢.

解决方案

在以下情况下,不使用volatile可能会导致错误:

  • 线程1输入getBar()并找到_barInstancenull
  • 线程1尝试创建Bar对象并将对_barInstance的引用更新.由于某些编译器优化,这些操作可能会无序进行.
  • 同时,线程2进入getBar()并看到非空的_barInstance,但可能会在_barInstance对象的成员字段中看到默认值.它实质上看到的是部分构造的对象,但引用不为null.

volatile修饰符将禁止对任何先前的读取或写入操作写入或读取变量_barInstance.因此,可以确保线程2不会看到部分构造的对象.

有关更多详细信息: http://www.cs.umd.edu/~pugh /java/memoryModel/DoubleCheckedLocking.html

As I understand, this is a correct implementation of the double-checked locking pattern in Java (since Java 5):

class Foo {
    private volatile Bar _barInstance;
    public Bar getBar() {
        if (_barInstance == null) {
            synchronized(this) { // or synchronized(someLock)
                if (_barInstance == null) {
                    Bar newInstance = new Bar();
                    // possible additional initialization
                    _barInstance = newInstance;
                }
            }
        }
        return _barInstance;
    }
}

I wonder if absence of volatile is a serious error or just a slight imperfection with possible performance drawback assuming _barInstance accessed only through getBar.

My idea is the following: synchronized introduces happens-before relation. The thread that initializes _barInstance writes its value to the main memory leaving the synchronized block. So there will be no double initialization of _barInstance even when it isn't volatile: other threads have null in theirs local copies of _barInstance (get true in the first check), but have to read the new value from the main memory in the second check after entering the synchronized block (get false and do no re-initialization). So the only problem is an excessive one-per-thread lock acquisition.

As I understand, it's correct in CLR and I believe it's also correct in JVM. Am I right?

Thank you.

解决方案

Not using volatile may result in errors in the following case:

  • Thread 1 enters getBar() and finds _barInstance to be null
  • Thread 1 attempts to create a Bar object and update the reference to _barInstance. Due to certain compiler optimisations, these operations may be done out of order.
  • Meanwhile, thread 2 enters getBar() and sees a non-null _barInstance but might see default values in member fields of the _barInstance object. It essentially sees a partially constructed object but the reference is not null.

The volatile modifier will prohibit a write or read of the variable _barInstance with respect to any previous read or write. Hence, it will make sure that thread 2 will not see a partially constructed object.

For more details: http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

这篇关于Java中的双重检查锁定易失的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆