(1)饿汉式(立即加载):
饿汉式单例在类加载初始化时就创建好一个静态的对象供外部使用,除非系统重启,这个对象不会改变,所以本身就是线程安全的。
Singleton通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。(事实上,通过Java反射机制是能够实例化构造方法为private的类的,会使Java单例实现失效)
- package com.atguigu.interview.chapter02;
- public class Singleton1 {
- private Singleton1() {
- System.out.println("构造函数Singleton1");
- }
- private static Singleton1 single = new Singleton1();
- public static Singleton1 getInstance() {
- System.out.println("getInstance");
- return single;
- }
- public static void main(String[] args){
- System.out.println("初始化");
- Singleton1 instance = Singleton1.getInstance();
- }
- }
- 懒汉式(延迟加载):
该示例虽然用延迟加载方式实现了懒汉式单例,但在多线程环境下会产生多个Singleton对象
- package com.atguigu.interview.chapter02;
- public class Singleton2 {
- private Singleton2() {
- System.out.println("构造函数Singleton2");
- }
- private static Singleton2 single = null;
- public static Singleton2 getInstance() {
- if(single == null){
- System.out.println("getInstance");
- single = new Singleton2();
- }
- return single;
- }
- public static void main(String[] args){
- System.out.println("初始化");
- Singleton2 instance = Singleton2.getInstance();
- }
- }
- 同步锁(解决线程安全问题):
在方法上加synchronized同步锁或是用同步代码块对类加同步锁,此种方式虽然解决了多个实例对象问题,但是该方式运行效率却很低下,下一个线程想要获取对象,就必须等待上一个线程释放锁之后,才可以继续运行。
- package com.atguigu.interview.chapter02;
- public class Singleton3 {
- private Singleton3() {}
- private static Singleton3 single = null;
- public static Singleton3 getInstance() {
- // 等同于 synchronized public static Singleton3 getInstance()
- synchronized(Singleton3.class){
- // 注意:里面的判断是一定要加的,否则出现线程安全问题
- if(single == null){
- single = new Singleton3();
- }
- }
- return single;
- }
- }
(4)双重检查锁(提高同步锁的效率):
使用双重检查锁进一步做了优化,可以避免整个方法被锁,只对需要锁的代码部分加锁,可以提高执行效率。
- package com.atguigu.interview.chapter02;
- public class Singleton4 {
- private Singleton4() {}
- private static Singleton4 single = null;
- public static Singleton4 getInstance() {
- if (single == null) {
- synchronized (Singleton4.class) {
- if (single == null) {
- single = new Singleton4();
- }
- }
- }
- return single;
- }
- }
在双重检查锁的情况下,依旧会有Bug的产生
怎么产生呢,假定当一万个数据不断请求获取single,第一次判断single == null以后,有某一个请求拿到锁,然后进行逻辑操作,在 single = new Singleton4(); 这一步的时候,new对象初始化,会先new一个地址值,
然后通过init初始化,
然后通过astore_1进行赋值,
[假定我们想要给到赋值为3]
我们知道,单线程情况下,当不影响最终结果一致性的情况下,指令是会重排序的。
就比如:x=1,
y=x+1
这个情况,两者就不能进行重排序,因为后面的操作依赖于前面的操作。
但是,通过init初始化,和通过astore_1进行赋值并没有互相依赖的情况,
所以是会造成指令重排序的。
所以,当我请求1走到new对象的时候,有可能初始化数据为0,还没有给到3这个数值,
而请求10来到了第一个判断
- public static Singleton4 getInstance() {
- if (single == null) {
- synchronized (Singleton4.class) {
- if (single == null) {
- single = new Singleton4();
- }
- }
- }
- return single;
- }
- }
此时就会判断,当前的sinfgle的数值为0,为什么呢?
因为我们 if (single == null) 判断,和synchronized 加锁里面的操作,是并行执行的。并不会互相影响。
所以当请求10拿到数据为0以后,就不会进入阻塞,而会进行后续的代码。形成数据不一致问题。
产生Bug
那么,在 private static Singleton4 single = null; 里面就需要添加volatile了,
那么,为什么添加了volatile以后,就一定可以限制指令重排序呢?我们知道,CPU并发控制:
防止打断的四种方式:
- 关中断
- 缓存一致性
- 系统屏障 [编译级别、指令级别]
- 总线/缓存锁
在我们的系统上,通过读取CPU的防打断策略,得到自身的方法。
而我们的volatile,是由C++写的,它的底层是通过内存屏障限制CPU的方式,进行防打断,
volatile有内存屏障
volatile 写
在每个 volatile 写操作的前面插入一个 StoreStore 屏障。在每个 volatile 写操作的后面插入一个 StoreLoad 屏障。
2、volatile 读在每个 volatile 读操作的后面插入一个 LoadLoad 屏障和一个 LoadStore 屏障。
添加了volatile以后,可以实现可见性,和禁止指令重排序。
(5) 静态内部类:
这种方式引入了一个内部静态类(static class),静态内部类只有在调用时才会加载,它保证了Singleton 实例的延迟初始化,又保证了实例的唯一性。它把singleton 的实例化操作放到一个静态内部类中,在第一次调用getInstance() 方法时,JVM才会去加载InnerObject类,同时初始化singleton 实例,所以能让getInstance() 方法线程安全。
特点是:即能延迟加载,也能保证线程安全。
静态内部类虽然保证了单例在多线程并发下的线程安全性,但是在遇到序列化对象时,默认的方式运行得到的结果就是多例的。
- package com.atguigu.interview.chapter02;
- public class Singleton5 {
- private Singleton5() {}
- private static class InnerObject{
- private static Singleton5 single = new Singleton5();
- }
- public static Singleton5 getInstance() {
- return InnerObject.single;
- }
- }
(6)内部枚举类实现(防止反射攻击):
事实上,通过Java反射机制是能够实例化构造方法为private的类的。这也就是我们现在需要引入的枚举单例模式。
- package com.atguigu.interview.chapter02;
- public class SingletonFactory {
- private enum EnumSingleton{
- Singleton;
- private Singleton6 singleton;
- //枚举类的构造方法在类加载是被实例化
- private EnumSingleton(){
- singleton = new Singleton6();
- }
- public Singleton6 getInstance(){
- return singleton;
- }
- }
- public static Singleton6 getInstance() {
- return EnumSingleton.Singleton.getInstance();
- }
- }
- class Singleton6 {
- public Singleton6(){}
}



