Java 由于内隐记忆障碍,双重检查锁定不是一个问题吗?

Java 由于内隐记忆障碍,双重检查锁定不是一个问题吗?,java,correctness,Java,Correctness,我正在查看一段代码,注意到设置会话锁的双重检查锁定实现: Lock lock = getLock(mySession); if (lock == null) { synchronized (myService.class) { lock = getLock(mySession); if (lock == null) { lock = new ReentrantLock(); setLock(mySession

我正在查看一段代码,注意到设置会话锁的双重检查锁定实现:

Lock lock = getLock(mySession);
if (lock == null) {
    synchronized (myService.class) {
        lock = getLock(mySession);
        if (lock == null) {
            lock = new ReentrantLock();
            setLock(mySession, lock);
        }
    }
}
这段代码片段中有一条评论说,开发人员假设属性存在内存障碍:CPU将刷新其缓存并直接从主存读取值


这是一个好的假设,还是最好的做法仍然是将“lock”定义为volatile来保证它?

假设您的代码是块或方法作用域:

public void mymethod() {
//...
Lock lock = getLock(mySession);
if (lock == null) {
    synchronized (myService.class) {
        lock = getLock(mySession);
        if (lock == null) {
            lock = new ReentrantLock();
            setLock(mySession, lock);
        }
    }
}
//...
}

双重检查问题根本不适用于它,因为
lock
是一个局部变量(分配在线程堆栈上),因此每个线程都可以看到它自己的副本,并且没有并发访问它的权限(这是双重检查问题的一个基本前提).

无论哪个成员变量实际持有传递给
setLock
的锁,都必须是可变的。否则,
getLock
可能会返回一个非空锁,但是
ReentrantLock
构造函数所做的更改可能没有传播到调用
getLock
的线程。请不要从“刷新”缓存的角度考虑-从更改可见性的角度考虑。如果在线程A上按某种顺序进行了两次更改,并且线程B看到了最后一次更改,则不能保证第一次更改也会被线程B看到,除非存在某种障碍:同步部分进入/退出、易失性分配/读取等等。问:为什么要使用lazy eval创建锁对象?你有没有证明它能以任何方式改进程序?简单地输入和离开
synchronized
块的成本可能高于构建一个从未使用过的新
ReentrantLock
的成本。嗯,我明白你对方法作用域
lock
变量的意思了。但是作为会话成员的锁(由
getLock
返回)呢?如果一个线程运行
setLock
,如果新线程看不到完整的实例化,那么新线程不能继续运行
setLock
?(如果我对上面的@Arkadiy注释的理解是正确的)。运行
setLock
的线程在同步块内执行此操作,因此另一个线程将无法进入,直到第一个线程从
setLock
返回。另外,
是在传递给
设置锁
之前构造的,因此它在该点完全构造。是的,我现在明白了。谢谢你的回答@齐柏林飞艇,问题是当
setLock
完成写入时,在同步块之外使用
getLock
的其他线程可能会得到一个非空对象,而初始化在原始线程之外并不完全可见。在这种特定情况下,您很幸运,
ReentrantLock
只有final和volatile字段,但对于任何具有常规字段的对象,您都有一个数据竞争。