一,概念
1.1)什么是单例模式:
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责自己创建自己的对象,同时确保永远只有这一个对象被创建。
这个类提供了一种外界访问其唯一对象的方式,外界可以直接访问自己创建好的自身对象 ,不能实例化该类的对象。
1.2)单例模式的意图:
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
二,单例模式的特性:
        1、单例就是该类只能返回⼀个实例,即,单例类只能有一个实例。
        2、单例类必须自己创建自己的唯一实例。
        3、单例类必须给外界提供访问这一实例的唯一方法。
三,单例代码的特点:
单例代码所具备的特点:
1、私有化的构造函数
2、私有的静态全局变量,最终指向自己创建的自身对象
3、供外界访问自己创建的自身对象的、公有的静态方法
四,关键代码:
判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
       构造函数是私有的。
五,代码实现——5种方式
        首先,我们将创建一个 SingleObject类。
SingleObject 类有它的私有构造函数、以及自身的一个静态全局实例。
SingleObject 类提供了一个静态方法,供外界获取它的这个静态全局实例。
5.1)懒汉式,线程不安全
描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。
特点:这种方式 lazy loading懒加载 很明显。
缺陷:在多线程环境下不能正常工作!懒汉式存在致命的问题:当有多个线程并行调用 getInstance() 的时候,就会创建多个实例。也就是说在多线程下不能正常工作。
public class Singleton {//私有的静态全局变量private static Singleton instance;//私有的构造方法private Singleton (){}//供外界访问获取类对象的函数:public static Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}}
自己编写:
public class SingleObject01 {//私有的构造函数:private SingleObject01() {}//提供外界访问的全局变量:private static SingleObject01 singleObject01;//供外界访问的方法:public static SingleObject01 getInstance() {if (singleObject01 == null) {singleObject01= new SingleObject01();}return singleObject01;}/*** 分析:* 这种懒汉式的严格来讲,在多线程访问的情况下,都不是单例模式了,* 因为如果有多个线程同时访问,那么得到的将是多个SingleObject01对象。*/}
5.1)懒汉式—加锁优化,线程安全 
描述:这种方式具备很好的 lazy loading懒加载效果,能够在多线程中很好的工作,但是效率很低,99% 情况下不需要同步。
缺点:虽然做到了线程安全,并且解决了多实例的问题,但是它并不高效。因为在任何时候只能有一个线程调用 getInstance() 方法,会严重影响效率。
public class Singleton {//私有的静态全局变量private static Singleton instance;//私有的构造函数private Singleton (){}//加锁之后的供外界访问获取类对象的方法public static synchronized Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}}
自己编写:
public class SingleObject02 {//私有构造函数:private SingleObject02() { }//私有的静态全局变量:private static SingleObject02 singleObject02;//供外界访问上边全局变量的方法:public static synchronized SingleObject02 getInstance() {if (singleObject02 == null) {singleObject02 = new SingleObject02();}return singleObject02;}}
注意:
        但是同步操作的代码其实只在第一次调用时才被需要用到,因为只有在第一次的时候才创建了单例实例对象时。
对于第二次、第三次等以后的调用,直接返回第一次的时候实例化好的对象即可。这就引出了双重检验锁。
5.2)双重检验锁
是否多线程安全:是
实现难度:较复杂
描述:这种方式采用双重检验锁机制,安全且在多线程情况下能保持高性能。
public class Singleton {//私有的静态全局变量private static Singleton singleton;//私有的构造函数private Singleton (){}//供外界访问获取类对象的方法:public static Singleton getSingleton() {//双重检验锁:if (singleton == null) {//锁住的范围是当前这个单例类:synchronized (Singleton.class) {if (singleton == null) {singleton = new Singleton();}}}//如果是第二次、第三次之后的访问,单例对象singleton已经在第一次被创建好了,所以直接返回该实例对象:return singleton;}}
这段代码看起来很完美,很可惜,它是有问题。主要在于instance = new Singleton()这句,这并非是一个原子操作,
事实上在 JVM 中这句话大概做了下面 3 件事情:
- 给 instance 分配内存
 - 调用 Singleton 的构造函数来初始化成员变量
 - 将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)
 
但是在 JVM 的即时编译器中存在指令重排序的优化。
也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。
如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),
所以线程二会直接返回 instance,然后使用,就会报错。
解决办法就是:我们只需要将 instance 变量声明成 volatile 就可以了。
使用 volatile 的主要原因是其一个特性:禁止指令重排序优化。
public class Singleton {//使用volatile关键字修饰的私有的静态全局变量private volatile static Singleton singleton;//私有的构造函数private Singleton (){}//供外界访问获取类对象的静态方法public static Singleton getSingleton() {//双重检验锁:if (singleton == null) {synchronized (Singleton.class) {if (singleton == null) {singleton = new Singleton();}}}return singleton;}}
自己编写:
public class SingleObject04 {//私有的构造函数:private SingleObject04() {}//私有的静态全局变量:private static volatile SingleObject04 singleObject04;//供外界获取该类对象的方法:public static SingleObject04 getInstance() {if (singleObject04 == null) {synchronized (SingleObject04.class) {if (singleObject04 == null) {singleObject04 = new SingleObject04();}else{return singleObject04;}}}return singleObject04;}}
5.3)饿汉式
        描述:这种方式比较常用,但容易产生垃圾对象。
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
它基于 classloader类加载机制 避免了多线程的同步问题,不过,类对象在类加载时就实例化,虽然导致类加载的原因有很多种,
在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类加载,
如果有其他的方式导致了类加载、从而初始化了 类对象instance ,显然没有达到 lazy loading懒加载的效果。
public class Singleton {//私有的静态全局变量,指向类加载时就实例化好的类对象;private static Singleton instance = new Singleton();//私有的构造函数private Singleton (){}//供外界访问获取类对象的静态方法:public static Singleton getInstance() {return instance;}}
自己编写:
public class SingleObject03 {//私有构造函数private SingleObject03() {}//私有的静态全局变量,饿汉式--直接提前实例化好private static SingleObject03 singleObject03 = new SingleObject03();//供外界访问的方法:public static SingleObject03 getInstance() {return singleObject03;}}
·
5.4)静态内部类
是否多线程安全:是
实现难度:一般
描述:这种方式能达到双检锁方式一样的功效,但实现更简单。这种方法也是《Effective Java》上所推荐的。
静态内部类写法的优势:
- 这种写法由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,又因为外部类加载时、并不需要立即加载内部类,因此该写法是懒汉式的、懒加载的;
 - 同时访问实例对象的时候,不需要进行同步措施,还能保证线程安全问题,没有性能缺陷;
PS:什么是懒加载:一开始不会创建实例对象、只有当访问该实例对象的时候才会去创建;
 
示例代码:
public class Singleton {//私有的构造函数:private Singleton (){}//静态内部类:private static class SingletonHolder {//供外界访问的静态常量-实例对象:private static final Singleton INSTANCE = new Singleton();}//供外界访问获取类对象的方法:public static Singleton getInstance() {return SingletonHolder.INSTANCE;}}
自己编写:
public class SingleObject05 {//私有的构造函数:private SingleObject05() {}//将静态全局变量放到静态内部类里边:private static class SingleObject05Inner{private static final SingleObject05 SINGLE_OBJECT_05=new SingleObject05();}//供外界访问的方法:public static SingleObject05 getInstance() {return SingleObject05Inner.SINGLE_OBJECT_05;}}
·
为什么静态内部类能够实现单例模式,且保证了线程安全?
我们刚才讲到,静态内部类的优点:
静态内部类写法的优势:
- 这种写法由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,又因为外部类加载时、并不需要立即加载内部类,因此该写法是懒汉式的、懒加载的;
 - 同时访问实例对象的时候,不需要进行同步措施,还能保证线程安全问题,没有性能缺陷;
PS:什么是懒加载:一开始不会创建实例对象、只有当访问该实例对象的时候才会去创建;
 
关于懒加载的优势:
外部类加载时并不需要立即加载内部类,内部类不被加载则不会去创建实例对象,故而可以节省内存。
具体来说当单例类第一次被加载时,并不需要去加载静态内部类,
只有当getInstance()方法第一次被调用时,才会导致JVM去加载该单例类的静态内部类。
这种方式,不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例对象的实例化。
为什么可以保证线程安全?
详情参见:
·
5.5)枚举
描述:这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
public enum Singleton {INSTANCE;public void whateverMethod() {}}
·
六,建议
一般情况下,不建议使用第 1 种非线程安全的懒汉式单例和第 2 种线程安全的懒汉方式,建议使用第 3 种饿汉方式。
只有在明确必须要实现 lazy loading 懒加载效果时,才使用第四种双重检验锁方式第 5 种静态内部类方式。
一般情况下直接使用饿汉式就好了,如果明确要求要懒加载(lazy initialization)会倾向于使用静态内部类,如果涉及到反序列化创建对象时会试着使用枚举的方式来实现单例。
PS:什么是 lazy loading 懒加载?
懒加载其实就是延时加载,即当对象需要用到的时候再去加载。
什么叫做需要用到的时候?比如说一个对象被创建出来就需要一笔内存开支,如果接下来就没有其他的操作,那可以认为这个对象创建得“过早”了。
好处:内存占用小。
