单例模式虽然简单,但是在Java中,细节实现还是有一些要注意的地方
实现方式
1. 懒汉式
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
上述实现方式最大的问题是不支持多线程,应为没有加锁synchronized。这么设计的主要目的是实现lazy loading
在多线程环境下,要对上述代码进行以下修改:
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
懒汉式的优点是lazy loading,在第一次调用才初始化,避免了内存浪费
缺点是必须加锁synchronized才能保证单例,加锁会影响执行效率
2. 饿汉式
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
饿汉式的好处是没有加锁,执行效率会提高。缺点是类加载时就初始化,会造成内存浪费。
3. 双检索/双重校验检索(DCL,即double-checked locking)
这种方式采用双锁机制,安全且在多线程情况下能保持性能。
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton(); //1
}
}
}
return singleton;
}
}
然而在实际的应用中,依然会存在问题。
简单来说,就是在上述代码//1行中,在Singleton构造函数执行前,变量instance可能变为非null的。
因为在创建对象的过程中,线程1可能会存在先分配内存但尚未初始化的情况,造成了返回的instance非空,此时线程2访问代码块时会返回这个尚未执行构造方法的instance引用。因此这里变量使用volatile保证顺序一致性和可见性就会非常重要。但是依然与JVM实现有关。
具体讨论可见一下参考文献:https://blog.csdn.net/chenchaofuck1/article/details/51702129
4. 登记式/静态内部类
这种方式是对饿汉式方式的一种优化,做到了只有在显示调用getInstance时才会创建新实例而不是在类加载时立即 创建,从而节省了内存资源
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
5. 枚举式
以上介绍的方式其实并不能真正实现单例模式,因为Java中存在反射机制,可以在运行时通过反射执行private构造方法创建对象。而枚举不存在这种问题。
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。