3

典型的懒惰单例:

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;
    }
}

为什么我们不关心与热切单例的同步,而不得不担心与他们的懒表亲的同步?

4

3 回答 3

1

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

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

也就是说,我建议将急切初始化标记为final。这将使您的意图更加清晰,并且编译器将强制执行急切初始化永远不会改变。如果是这样,那么将再次需要并发控制。

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

仅供参考,如果您有兴趣, Java 虚拟机规范的第 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) 来确保只有一个线程执行初始化并正确共享结果。

于 2013-03-03T11:56:33.973 回答
0

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

于 2013-03-02T19:22:58.063 回答
0

因为在后一个示例中,单例的实例在getInstance被调用时总是存在的——这里没有什么要同步的。这与第一个示例相反,其中实例还没有必要初始化。在这种情况下,包含需要保护(例如通过同步)以防止同时访问的getInstance关键部分(及其主体)。if

于 2013-03-02T19:26:49.427 回答