信号量能否安全地使用双重检查锁定习语?

时间:2010-11-02 07:08:34

标签: java multithreading thread-safety semaphore

java中的以下线程是否安全?

public class TestDCL{
    private static final Semaphore lock = new Semaphore(1);
    private Object instance;

    public Object m(){
        if(instance == null){
            lock.acquire();
            if(instance == null){
                instance = new Object();
            }
            lock.release();
        }
        return instance; 
    }
}

3 个答案:

答案 0 :(得分:2)

这不是线程安全的。语句new Object();不是原子操作。当为其分配内存时,instance不再是null。在为if分配内存之后到达第一个instance条件的新线程,但在调用其构造函数之前将返回部分构造的对象。如果您正在尝试实现一个线程安全的单例,请使用Bill Pugh's solution,它既是线程安全的又是惰性的。

答案 1 :(得分:1)

同意蒂姆先前的帖子。易失性使得可见性和双重检查锁定的原因被描述为clever but broken是围绕部分构造的对象(缓存一致性/ JVM优化)。

正如蒂姆所说,这一切都在格茨的书中,但我想提出一个关于懒惰初始化的观点。为什么这样?根据我的经验,它通常不需要,而且你在多线程环境中运行,并且真正关心初始化安全性 - 你引入了很多可变性和复杂性,这很难测试。

我也强调旧警告不要尽早优化。您是否知道粗粒度同步会降低应用程序的速度?通常,它的锁争用速度慢,而不是syncrhonization关键字per-sa。使用syncrhonized和DCL的快速测试将确认。

答案 2 :(得分:0)

答案是,这取决于。你的instance是可变的还是一成不变的?如果instance包含可变状态,则必须将其声明为volatile。您必须这样做的原因与@Vijay Mathew建议的部分构造的对象无关,而是与线程可见性有关。在一个线程中对单例实例的状态所做的更改不一定对另一个线程可见。使用volatile关键字可确保硬件将使用某种机制来刷新缓存以消除陈旧数据。

Java Concurrency in Practice,第16.2.3章概述了安全初始化习语,并包含一个惰性初始化习惯用法。 16.2.4讨论了双重检查锁定习语。