为什么我们要同步懒惰的单身而不是热切的单身?

时间:2013-03-02 19:20:04

标签: oop synchronization singleton lazy-initialization

典型的懒惰单身人士:

public class Singleton {
    private static Singleton INSTANCE;

    private Singleton() {

    }

    public static synchronized Singleton getInstace() {
        if(INSTANCE == null)
            INSTANCE = new Singleton();

        return INSTANCE;
    }
}

典型的热切单身人士:

public class Singleton {
    private static Singleton INSTANCE = new Singleton();

    public static Singleton getInstance() {
        return INSTANCE;
    }
}

为什么我们不关心与热切的单身人士的同步,但是不得不担心与他们的懒人表兄弟同步?

3 个答案:

答案 0 :(得分:1)

Eager实例化不需要显式同步来共享字段的引用,因为JVM已经为我们处理了它作为类加载机制的一部分。

为了更详细地说明,在一个类可供任何线程使用之前,它将被加载,验证和初始化。编译器将静态字段分配重写到此初始化阶段,并通过Java内存模型的规则和底层硬件体系结构将确保访问该类的所有线程都将看到该类的该类。这意味着JVM将为我们处理任何硬件障碍等。

那就是说,我建议标记热切的初始化决赛。这将使您的意图更清晰,编译器将强制执行急切初始化永不改变。如果确实如此,则需要再次进行并发控制。

private static **final** Singleton INSTANCE = new Singleton();

仅供参考。如果您有兴趣,Java Virtual Machine Specification的第5.5节将更详细地介绍这一点。来自规范的几个选择片段是

*"Because the Java Virtual Machine is multithreaded, 
initialization of a class or interface requires careful 
synchronization"*

*"For each class or interface C, there is a unique initialization lock LC"*

*9&10) "Next, execute the class or interface initialization method of C"
"If the execution of the class or interface initialization 
method completes normally, then acquire LC, label the Class object for 
C as fully initialized, notify all waiting threads, release LC, and 
complete this procedure normally."*

在规范的第10步中,将设置静态字段,并使用锁(LC)来确保只有一个线程执行初始化并且结果正确共享。

答案 1 :(得分:0)

由于当类首次加载到内存(jit)时初始化了一个急切的单例,并且这只发生一次。但是,如果两个客户端将尝试同时从两个线程调用单例实例方法,则可能会创建两个单例。

答案 2 :(得分:0)

因为在后一个示例中,当调用getInstance时,Singleton的实例始终存在 - 此处无需同步。这与实例尚未初始化的第一个例子相反。在这种情况下,getInstance包含关键部分(if及其正文),需要对同时访问进行保护(例如,通过同步)。