1. 单例模式的概述
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例
2. 单例模式结构图
3. 饿汉式单例模式
代码实现
package com.zach.pattern.singleton;
/**
* @Author:Zach
* @Description: 饿汉式单例模式
* @Date:Created in 10:57 2018/8/9
* @Modified By:
*/
public class EagerSingleton {
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton(){} //private修饰防止在类的外部通过new实例化对象
public static EagerSingleton getInstance(){
return instance;
}
}
当类被加载时,静态变量instance会被初始化,此时私有类的私有构造函数会被调用,创建唯一的实例
4. 懒汉式单例模式
懒汉式单例在第一次调用getInstance()方法时实例化,在类加载时并不自行实例化,这是延迟加载的技术,但是这并不是线程安全的
可通过DCL,即Double Check Lock,中卫双重检查锁定
package com.zach.pattern.singleton;
/**
* @Author:Zach
* @Description: 懒汉式单例模式
* @Date:Created in 11:10 2018/8/9
* @Modified By:
*/
public class LazySingleton {
private static LazySingleton ls;
private LazySingleton(){}
public static LazySingleton getInstance(){
if(ls ==null) {
synchronized (LazySingleton.class){
if(ls == null){
ls = new LazySingleton();
}
}
}
return ls;
}
}
上面的代码实现分析
1) 如果第一个singleton不为null,则不需要执行下面的加锁动作
2) 如果第一个singleton为null,即使有多个线程同一时间判断,但是由于synchronize存在,只有一个线程能够创建对象
3) 当第一个获取锁的线程创建完singleton对象后,其他的在第二次判断singleton一定不会为null,则直接返回已经创建好的singleton对象;
上面的代码看起来分析都没有错,但是通过查看资料发现,这也是错误的(有点震惊),先看看对象创建的过程
1) 分配内存空间
2) 初始化对象
3) 将内存空间的地址赋值给对应的引用
由于重排序的缘故,步骤2,3可能会发生重排序,其过程如下:
1) 分配内存空间
2) 将内存空间的地址赋值给对应的引用
3)初始化对象
如果2,3 发生重排序就会导致第二个判断出错,singleton !=null,但它其实仅仅只是一个地址而已,此时对象还没有被初始化,所以return 的singleton对象是一个没有被初始化的对象,所以DCL错误的根源在于: ls = new lazySingleton();
5. 基于volatile的解决方案
package com.zach.pattern.singleton;
/**
* @Author:Zach
* @Description: 懒汉式单例模式
* @Date:Created in 11:10 2018/8/9
* @Modified By:
*/
public class LazySingleton {
//通过volatile关键字来确保安全
private volatile static LazySingleton ls;
private LazySingleton(){}
public static LazySingleton getInstance(){
if(ls ==null) {
synchronized (LazySingleton.class){
ls = new LazySingleton();
}
}
return ls;
}
}
singleton声明为volatile后,步骤2与3就不会被重排序了,问题就解决
6. 基于类初始化的解决方案
该解决方案的根本在于: 利用classloader的机制来保证初始化instance时只有一个线程,JVM在类初始化阶段会获取一个锁,这个锁可以同步多个线程对同一个类的初始化
public class Singleton {
private static class SingletonHolder {
public static Singleton singleton = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.singleton;
}
}
该解决方案的实质是允许步骤2,3重排序,但是不允许其他线程看见
Java语言规定,对于每一个类或者接口C,都有一个唯一的初始化锁LC与之相对应。从C到LC的映射,由JVM的具体实现去自由实现。JVM在类初始化阶段期间会获取这个初始化锁,并且每一个线程至少获取一次锁来确保这个类已经被初始化过了
7. 参考资料