参考:
https://www.cnblogs.com/zhaoyan001/p/6365064.html
1. 恶汉式
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
利用静态成员常量创建单例模式,简单、线程安全。
原理
类的静态常量,在类加载过程的准备阶段已经完成初始化,由JVM控制。
2. 懒汉式
public class Singleton {
private static Class Inner {
public static Singleton instance = new Singleton();
}
private Singleton() {
}
public static Singleton getInstance() {
return Inner.instance;
}
}
利用静态内部类创建单例模式,线程安全。
原理
一、根据Java语言规范,在首次发生下列任意一种情况时,一个类或接口类型T将被立即初始化(类加载的初始化阶段,此时加载、验证、准备已在此之前开始)。
- T是一个类,而且一个T类型的实例被创建;
- T是一个类,而且T中声明的一个静态方法被调用;
- T中声明的一个静态字段被赋值;
- T中声明的一个静态字段被使用,且这个字段不是一个常量字段;
- T是一个顶级类,而且一个断言语句嵌套在T内部被执行。
二、Java语言规范规定,对于每一个类或接口C,都有一个唯一的初始化锁LC与之对应。
- 当两个线程A和B试图同时初始化一个类(Class对象)时,会先去竞争Class对象的初始化锁,假设线程A获取到了初始化锁,那么线程B将一直等待获取初始化锁;
- 线程A发现Class对象还没有被初始化,开始执行类的静态初始化和初始化类中声明的静态字段;
- 线程A初始化完毕,唤醒所有等待初始化锁的线程,然后释放初始化锁;
- 线程B获取到初始化锁,根据锁的happens-before关系,可以确保线程A执行类的初始化时的写入操作,线程B一定可见,于是线程B会发现类已经初始化完毕,线程B释放初始化锁。
利用初始化锁控制线程安全性。
public class Singleton {
private static volatile Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (null == instance) {
synchronized (Singleton.class) {
if (null == instance) {
instance = new Singleton();
}
}
}
return instance;
}
}
利用volatile+双重检查创建单例模式,线程安全。
原理
创建一个对象instance = new Singleton()
,大致分为:
- 分配对象的内存空间;
- 初始化对象;
- 设置instance指向分配的内存地址。
如果不使用volatile变量,而是用普通变量的话:
在单线程中,2 和 3 是可以重排序的,并不影响结果。
在多线程中,2 和 3 重排序将可能导致未初始化完毕的对象在最外面一层判断null == instance
时为false,而误认为对象已经初始化完毕了。
利用volatile将禁止2 和 3 的重排序,保证instance != null
时,对象必定已经初始化完成。