定义
单例模式: 确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
使用场景
避免产生多个对象消耗过多的资源,或者某种类型的对象只有一个。
1、饿汉模式
public static class Singleton {
private static final instance Singleton = new Singleton();
private Singleton() { // 私有构造函数
}
public static Singleton getInstance() {
return instance;
}
}
instance是静态成员变量,在声明时就被实例化。
2、懒汉模式
public static class Singleton {
private static final instance Singleton = null;
private Singleton() { // 私有构造函数
}
public static synchronized Singleton getInstance() {
if(instance == null) {
instance = new Singleton()
}
return instance;
}
}
instance只是在初次调用时初始化,synchronized,每次调用getInstance()方法都会进行同步,这样会消耗不必要的资源,这也是懒汉模式最大的问题。
3、Double Check Lock(DCL)
public static class Singleton {
private static final instance Singleton = null;
private Singleton() { // 私有构造函数
}
public static Singleton getInstance() {
if(instance == null) {
synchronized(Singleton.class) {
if(instance == null) {
instance = new Singleton()
}
}
}
return instance;
}
}
DCL有两次判空,第一次判空,是为了避免不必要的同步;第二次判空,是为了初次初始化时,只初始化一次。
instance = new Singletone(); 这行代码最终会编译成多条汇编指令,它大致做了三件事:
- 给Singleton的实例分配内存;
- 调用Singleton()的构造函数,初始化成员字段;
- 将instance对象指向分配的内存空间(此时instance不再是null了);
DCL失效情况:
由于Java编译器允许处理器乱序执行,以及JDK1.5之前JMM(Java Memory Model)中Cache、寄存器到内存回写顺序的规定,上面第二和第三的顺序是无法保证的。也就是说有可能JVM会为新的Singleton实例分配空间,然后直接赋值给instance成员,然后再去初始化这个Singleton实例,这样就可能出错了。我们以A、B两个线程为例:
- A、B线程同时进入了第一个if判断
- A首先进入synchronized块,由于instance为null,所以它执行instance = new Singleton();
- 由于JVM内部的优化机制,JVM先画出了一些分配给Singleton实例的空白内存,并赋值给instance成员(注意此时JVM没有开始初始化这个实例),然后A离开了synchronized块。
- B进入synchronized块,由于instance此时不是null,因此它马上离开了synchronized块并将结果返回给调用该方法的程序。
- 此时B线程打算使用Singleton实例,却发现它没有被初始化,于是错误发生了。
在JDK1.5之后,SUN公司调整了JVM,增加了volatile关键字,只需将instance的定义改成private volatile static Singleton instance = null就可以保存instance对象每次都是从主内存总读取。
4.静态内部类单例模式
public static class Singleton {
private Singleton() { // 私有构造函数
}
private static class SingletonHolder {
private static final instance Singleton = new Singleton();
}
public static synchronized Singleton getInstance() {
return SingletonHolder.instance;
}
}
当第一次加载Singleton类时,并不会初始化,只有当第一次调用getInstance()方法,会导致虚拟机加载SingletonHolder类,此时才是初始化。利用的Java类加载机制
不同的类装载器,可能会导致产生多个单例对象:
public static Class getClass(String className) throws ClassNotFoundException {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
if (cl == null) {
cl = Singleton.class.getClassLoader();
}
return cl.loadClass(className);
}
如果Singleton实现了Serializable接口,反序列化时可能产生多个实例对象:
private Object readResolve() { // 重写readResolve方法
return instance;
}