作者: 霍英俊 [huo920@live.com]
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类 只能存在一个对象实例 ,并且该类只提供一个取得其对象实例的方法 - 静态方法。
比如 Hibernate 的 SessionFactory ,它充当数据存储源的代理,并负责创建 Session 对象。 SessionFactory 并不是轻量级的,一般情况下,一个项目通常只需要一个 SessionFactory 就够,这时就会使用到单例模式。
创建型设计模式 - 单例模式
设计原则:无
常用场景:应用中有对象需要是全局的且唯一
使用概率:99.99999%
复杂度:低
变化点:无
选择关键点:一个对象在应用中出现多个实例是否会引起逻辑上或者是程序上的错误
逆鳞:在以为是单例的情况下,却产生了多个实例
相关设计模式
原型模式:单例模式是只有一个实例,原型模式每拷贝一次都会创造一个新的实例。
单例模式有八种方式:
饿汉式 - 静态常量 - 推荐使用饿汉式 - 静态代码块 - 推荐使用懒汉式 - 线程不安全懒汉式 - 线程安全,同步方法懒汉式 - 线程安全,同步代码块懒汉式 - 双重检查 - 推荐使用懒汉式 - 静态内部类 - 推荐使用懒汉式 - 枚举 - 推荐使用步骤如下
构造器私有化防止 new类的内部创建对 象向外暴露一个静态的公共方法。 getInstance代码实现 public class SingletonTest01 { public static void main(String[] args) { Singleton instance = Singleton.getInstance(); Singleton instance2 = Singleton.getInstance(); System.out.println(instance == instance2); System.out.println(instance.hashCode()); System.out.println(instance2.hashCode()); } } /** * 单例模式 * 饿汉式 静态常量 */ class Singleton { // 1. 私有化构造器,防止外部 new private Singleton() { } // 2. 提供一个静态常量属性对应返回的单例类型 private static final Singleton instance = new Singleton(); // 3. 提供一个 public static 方法供外界获取该实例 public static Singleton getInstance() { return instance; } }代码演示
public class SingletonTest02 { public static void main(String[] args) { Singleton instance = Singleton.getInstance(); Singleton instance2 = Singleton.getInstance(); System.out.println(instance == instance2); System.out.println(instance.hashCode()); System.out.println(instance2.hashCode()); } } /** * 单例模式 * 饿汉式 静态块 */ class Singleton { // 1. 私有化构造器,防止外部 new private Singleton() { } // 2. 声明一个静态成员变量 private static Singleton instance; // 3. 在静态代码块中实例化 static { instance = new Singleton(); } // 4. 提供一个 public static 方法供外界获取该实例 public static Singleton getInstance() { return instance; } }代码演示
package com.huo.singleton.type03; /** * @author 霍英俊 * @version 1.0.0 * @className: SingletonTest03 * @description: TODO * @email huo920@live.com * @date 2020/7/4 17:19 */ public class SingletonTest03 { public static void main(String[] args) { Singleton instance = Singleton.getInstance(); Singleton instance2 = Singleton.getInstance(); System.out.println(instance == instance2); System.out.println(instance.hashCode()); System.out.println(instance2.hashCode()); } } /** * 懒汉式 - 线程不安全 */ class Singleton{ private static Singleton singleton; private Singleton(){ } // 当调用 getInstance 方法时该创建单例对象, public static Singleton getInstance(){ if (singleton == null){ singleton = new Singleton(); } return singleton; } }代码演示
package com.huo.singleton.type04; /** * @author 霍英俊 * @version 1.0.0 * @className: SingletonTest03 * @description: TODO * @email huo920@live.com * @date 2020/7/4 17:19 */ public class SingletonTest04 { public static void main(String[] args) { Singleton instance = Singleton.getInstance(); Singleton instance2 = Singleton.getInstance(); System.out.println(instance == instance2); System.out.println(instance.hashCode()); System.out.println(instance2.hashCode()); } } /** * 懒汉式 - 线程安全, 同步方法 */ class Singleton { private static Singleton singleton; private Singleton() { } // 当调用 getInstance 方法时该创建单例对象,懒汉式 // 加入同步处理的代码,解决了线程安全的问题 public static synchronized Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; } }1)解决了线程不安全问题 2)效率太低了,每个线程在想获得类的实例时候,执行 getInstance() 方法都要进行 同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例, 直接 return 就行了。方法进行同步效率太低 3)结 论: 在实际开发中, 不推荐使用这种方式
实际上并没有解决线程安全问题
代码演示
package com.huo.singleton.type05; /** * @author 霍英俊 * @version 1.0.0 * @className: SingletonTest03 * @description: TODO * @email huo920@live.com * @date 2020/7/4 17:19 */ public class SingletonTest05 { public static void main(String[] args) { Singleton instance = Singleton.getInstance(); Singleton instance2 = Singleton.getInstance(); System.out.println(instance == instance2); System.out.println(instance.hashCode()); System.out.println(instance2.hashCode()); } } /** * 懒汉式 - 线程安全, 同步代码块 */ class Singleton { private static Singleton singleton; private Singleton() { } // 当调用 getInstance 方法时该创建单例对象,懒汉式 // 在第四种方法的基础上将同步代码块方法下面,为了解决效率问题 // 实际上多线程的情况可能有两个线程同时走进if (singleton == null), 所以并没有解决线程安全问题 public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class){ singleton = new Singleton(); } } return singleton; } }代码演示
package com.huo.singleton.type06; /** * @author 霍英俊 * @version 1.0.0 * @className: SingletonTest03 * @description: TODO * @email huo920@live.com * @date 2020/7/4 17:19 */ public class SingletonTest06 { public static void main(String[] args) { Singleton instance = Singleton.getInstance(); Singleton instance2 = Singleton.getInstance(); System.out.println(instance == instance2); System.out.println(instance.hashCode()); System.out.println(instance2.hashCode()); } } /** * 懒汉式 - 双重检查 */ class Singleton { // 这里必须加volalite的原因 - volalite可以禁止指令重排: // new 对象的过程分为三步: // 1.分配空间 // 2.初始化对象 // 3.指向对象内存地址。 // 2和3可能被编译器自动重排,导致判断非空但是实际拿的对象还未完成初始化 private static volatile Singleton singleton; private Singleton() { } // 当调用 getInstance 方法时该创建单例对象,懒汉式 // 进行了两次 if (singleton == null) 检查,这样就可以保证线程安全了,同时解决了懒加载问题 public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class){ if (singleton == null){ singleton = new Singleton(); } } } return singleton; } }如果不加 volatile 的话,虽然加了锁和双重判断,但是其实这个类是线程不安全的
原因是:
这个要从cpu的指令开始说起
当我们执行 instance = new Singleton(); 时 要执行什么操作呢?
主要分三步:
分配对象内存空间 memory = allocate()分配对象内存空间
ctorInstance()初始化对象
instance = memory 设置instance指向刚分配的内存
但是由于存在指令重排的情况(单线程情况下无影响。多线程下会有影响)
由于2 和3 没有顺序的必然关系
也就可能会出现:
分配对象内存空间 memory = allocate()分配对象内存空间
instance = memory 设置instance指向刚分配的内存
ctorInstance()初始化对象
此时我们假设有两个线程A和B进入
A 先执行了 3.instance = memory 设置instance指向刚分配的内存这个操作,但是还未来得及初始化对象。
B 判断 if(instance == null) 时 则会返回true 然后instance, 这时返回值就会出现问题 。
解决方案:
此时使用volatile关键字 则可以解决这个问题 volatile 关键字 有禁止重排序的功能 原文链接:https://blog.csdn.net/Minstrel007/article/details/94592467
代码演示
package com.huo.singleton.type07; /** * @author 霍英俊 * @version 1.0.0 * @className: SingletonTest03 * @description: TODO * @email huo920@live.com * @date 2020/7/4 17:19 */ public class SingletonTest07 { public static void main(String[] args) { Singleton instance = Singleton.getInstance(); Singleton instance2 = Singleton.getInstance(); System.out.println(instance == instance2); System.out.println(instance.hashCode()); System.out.println(instance2.hashCode()); } } /** * 懒汉式 - 静态内部类 */ class Singleton { private Singleton() { } // Singleton 被装载的时候,静态内部类 SingletonInstance 并不会立即被装载 private static class SingletonInstance { private static final Singleton INSTANCE = new Singleton(); } // 当调用 getInstance 方法时该创建单例对象 // getInstance 会导致静态内部类 SingletonInstance 被装载,而且只会被装载一次,不会有线程安全问题 public static Singleton getInstance() { return SingletonInstance.INSTANCE; } }代码演示
package com.huo.singleton.type08; /** * @author 霍英俊 * @version 1.0.0 * @className: SingletonTest03 * @description: TODO * @email huo920@live.com * @date 2020/7/4 17:19 */ public class SingletonTest08 { public static void main(String[] args) { Singleton instance = Singleton.INSTANCE; Singleton instance2 = Singleton.INSTANCE; System.out.println(instance == instance2); System.out.println(instance.hashCode()); System.out.println(instance2.hashCode()); instance.method(); } } /** * 懒汉式 - 枚举 */ enum Singleton { INSTANCE; public void method(){ System.out.println("Singleton - enum"); } }单例模式在JDK 应用的源码分析
我们 JDK 中 java.lang.Runtime 就是经典的 例模式饿汉式代码分析 +Debug 源码代码说明