# 单例模式 ### 涉及的JVM 与 CPU Java虚拟机在执行该对象创建语句时具体做了哪些事情,我们简单概括为3步: - 1 在栈内存中创建singleton 变量,在堆内存中开辟一个空间用来存放Singleton实例对象,该空间会得到一个随机地址,假设为0x0001。 - 2 对Singleton实例对象初始化。 - 3 将singleton变量指向该对象,也就是将该对象的地址0x0001赋值给singleton变量,此时singleton就不为null了。 CPU在执行指令时并不是无节操随意打乱顺序,而是有一定的原则可寻的,这个原则也叫先行发生原则(happens-before) **先行发生原则(happens-before):** - 1 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作 - 2 锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作 - 3 volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作 - 4 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C - 5 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作 - 6 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生 - 7 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行 - 8 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始 ## 单例模式介绍 所谓单例,就是整个程序有且仅有一个实例。该类负责创建自己的对象,同时确保只有一个对象被创建。在Java,一般常用在工具类的实现或创建对象需要消耗资源。 #### 特点 - 类构造器私有 - 持有自己类型的属性 - 对外提供获取实例的静态方法 ##### 饿汉模式 线程安全,比较常用,但容易产生垃圾,因为一开始就初始化 ``` public class Singleton { private static Singleton instance = new Singleton(); private Singleton (){} public static Singleton getInstance() { return instance; } } 直接调用 Singleton.getInstance() 来创建对象 ``` 只要这个类一加载完,就会创建实例对象,显得很着急,像一个恶汉一样,所以称之为恶汉式单例模式。 由于饿汉模式会带来一个问题,类加载的时候静态变量便申请了空间,如果没有用到的话,而且复杂的话,会造成大量的资源浪费,所以用一种懒加载的方式来创建对象,等需要用到这个对象的时候再创建。 ##### 懒汉模式 线程不安全,延迟初始化,严格意义上不是不是单例模式 ``` public class Singleton { private static Singleton instance; // 静态类型 private Singleton (){} public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } 直接调用 Singleton.getInstance() 来创建对象 ``` 由于只有当用到时才创建对象,比较懒,我们称之为懒汉式单例模式。 ##### 双重锁模式 线程安全,延迟初始化。这种方式采用双锁机制,安全且在多线程情况下能保持高性能。 ``` public class Singleton { private volatile static Singleton singleton; private Singleton() { System.out.print("实例化对象\n"); } public static Singleton getInsatnce() { if (singleton == null) {//-----------------------------1 synchronized (Singleton.class) {//------------2 if (singleton == null) { singleton = new Singleton(); } } } return singleton; } } ``` 这里synchronized 的用法和上面的有所不同,上面我们用synchronized 来修饰方法,表示给整个方法上了把锁,我们称之为同步方法。这里我们只给语句1和语句2加了把锁,我们称这种结构为同步代码块,同步代码块synchronized 后面的括号中需要一个对象,可以任意,这里我们用了Singleton的类对象Singleton.class。可以看到我们在方法中进行了两次对象是否为空的判断,一次在同步代码块外面,一次在里面。因此称之为双重检验锁模式(Double Checked Locking Pattern)。 双重检查模式,进行了两次的判断,第一次是为了避免不要的实例,第二次是为了进行同步,避免多线程问题。由于singleton=new Singleton()对象的创建在JVM中可能会进行重排序,在多线程访问下存在风险,使用volatile修饰signleton实例变量有效,解决该问题。 ##### 静态内部类单例模式 ``` public class Singleton { private Singleton(){} public static Singleton getInstance(){ return Inner.instance; } private static class Inner { private static final Singleton instance = new Singleton(); } } ``` 只有第一次调用getInstance方法时,虚拟机才加载 Inner 并初始化instance ,只有一个线程可以获得对象的初始化锁,其他线程无法进行初始化,保证对象的唯一性。目前此方式是所有单例模式中最推荐的模式,但具体还是根据项目选择。 我们在Singleton 类内部定义一个Inner 静态内部类,里面有一个对象实例,当然由于Java虚拟机自身的特性,只有调用该静态内部类时才会创建该对象,从而实现了单例的延迟加载,同样由于虚拟机的特性,该模式是线程安全的,并且后续读取该单例对象时不会进行同步加锁的操作,提高了性能。 ##### 枚举单例模式 - 枚举类隐藏了私有的构造器。 - 枚举类的域 是相应类型的一个实例对象 ``` public enum Singleton { INSTANCE // doSomething 该实例支持的行为 // 可以省略此方法,通过Singleton.INSTANCE进行操作 public static Singleton get Instance() { return Singleton.INSTANCE; } } ``` ### 参考如下: [Java 大白话讲解设计模式之 -- 单例模式](https://www.jianshu.com/p/b99e870f4ce0)