设计模式之单例模式
概述
- 单例模式:确保一个类只有一个实例,并提供一个全局访问点。
- 单例设计模式,是采取一定的方法措施以保证在整个系统中,某个类只能存在一个对象实例,并且这个类只提供静态方法用来获取该对象实例。
理解
简单例子
- 我们使用笔记本时,会将不用的文件移入到废纸篓(mac)或者回收站(windows)中,对于笔记本来说,废纸篓或者回收站就是唯一一个实例。
- 一个公司的董事长A的职位只有一个,对于该公司而言,A就是该公司的唯一的董事长的实例。
- 同理,在软件开发过程中,开发一个系统时,有一些实例在系统中要求只能存在一个对象实例,为了确保在系统中该类只有一个实例,故引出了⏤⏤单例模式
条件
- 构造方法为私有
private
,以确保无法在该类之外通过new
操作符创建对象; - 类本身提供一个静态
static
方法用来获取该对象实例; - 在类内部调用私有的构造方法创建静态
static
私有private
的实例化对象,因为类方法是静态的,静态方法访问静态的实例,所以该成员变量也应该是静态的。
核心代码
饿汉式
-
随着类的加载而加载,在类加载时便初始化实例,可以避免线程同步问题,线程安全。但是无延迟加载,某些情况下,会造成内存浪费。
-
饿汉式,使用静态常量,不推荐。
class Singleton1 { private Singleton1(){} //类初始化时给该成员变量赋值,可以加final关键字 //静态常量 private static final Singleton1 Instance = new Singleton1(); public static Singleton1 getInstance(){ return Instance; } }
-
饿汉式,使用静态代码块,不推荐。
class Singleton2 { private Singleton2(){} private static Singleton2 Instance; //静态代码块 static { Instance = new Singleton2(); } public static Singleton2 getInstance(){ return Instance; } }
懒汉式
-
支持延迟加载;但是会出现频繁加锁、释放锁,以及并发度低的问题。
-
懒汉式 ,线程不安全,多线程情况下,不能保证单个实例,不推荐。
class Singleton3 { private Singleton3(){} private static Singleton3 Instance; public static Singleton3 getInstance(){ if (Instance == null){ Instance = new Singleton3(); } return Instance; } }
-
懒汉式,线程安全,方法上加
synchronized
关键字来保证线程安全,调用getInstance()
该方法时,每次都要同步,效率低,不推荐。class Singleton4 { private Singleton4(){} private static Singleton4 Instance; //使用 synchronized 关键字保证线程安全 同步方法 public static synchronized Singleton4 getInstance(){ if (Instance == null){ Instance = new Singleton4(); } return Instance; } }
双重锁校验DCL
-
支持延迟加载,支持高并发,双重校验,推荐。
- 第一次:如果存在已经实例化的实例,直接返回,不执行同步方法里的代码,提高性能。
- 第二次:多线程情况下,如果不加第二次校验,A线程、B线程同时通过了第一次的校验,则最终会产生两个实例,不能保证单实例。
-
缺点:多线程环境中,指令重排可能导致访问获得一个未初始化的对象。解决方案便是禁止多线程情况下的指令重排序,使用
volatile
关键字修饰Instance
。class Singleton5 { private Singleton5(){} //volatile关键字 防止指令重排 private static volatile Singleton5 Instance; public static Singleton5 getInstance(){ if(Instance == null){ synchronized(Singleton5.class){ if (Instance == null){ Instance = new Singleton5(); } } } return Instance; } }
静态内部类
-
支持延迟加载、支持高并发、代码量比DCL少,推荐。
-
静态成员变量与静态代码块在类被调用的时候才会初始化,而静态内部类只有当被外部类调用到的时候才会初始化。也就是说,只有调用
getInstance()
时,才会被加载,这就保证了延迟加载的特点。class Singleton6 { private Singleton6() {} private static class Singleton6Instance{ private static final Singleton6 Instance = new Singleton6(); } public static Singleton6 getInstance(){ return Singleton6Instance.Instance; } }
枚举类
-
可以避免多线程同步问题,可以防止反序列化重新创建新的对象,推荐。
enum Singleton7 { Instance; }
反思
-
我们知道,现有的单例模式写法一般推荐的都是DCL、静态内部类和枚举类;我们也知道
java
反射可以在运行时创建类的实例,那么如果使用反射创建类实例,上面三种还能保证单个实例么? -
DCL 反射不安全,通过反射可以得到另外的实例;
//DCL使用反射 并比较是否是同一个实例 Singleton5 singleton5one = Singleton5.getInstance(); Singleton5 singleton5two = null; Class<Singleton5> clazz = Singleton5.class; Constructor<Singleton5> constructor = clazz.getDeclaredConstructor(null); constructor.setAccessible(true); singleton5two = constructor.newInstance(); System.out.println("singleton5one=" + singleton5one); System.out.println("singleton5two=" + singleton5two); //DCL打印结果: //singleton5one=com.practice.singleton.type8.Singleton5@63947c6b //singleton5two=com.practice.singleton.type8.Singleton5@2b193f2d
-
静态内部类,反射不安全;
//静态内部类使用反射 Singleton6 singleton6one = Singleton6.getInstance(); Singleton6 singleton6two = null; Class clazz6 = Singleton6.class; Constructor<Singleton6> constructor6 = clazz6.getDeclaredConstructor(null); constructor6.setAccessible(true); singleton6two = constructor6.newInstance(); System.out.println("singleton6one=" + singleton6one); System.out.println("singleton6two=" + singleton6two); //静态内部类打印结果 //singleton6one=com.practice.singleton.type8.Singleton6@355da254 //singleton6two=com.practice.singleton.type8.Singleton6@4dc63996
-
枚举类,反射安全。
//枚举类使用反射 Singleton7 singleton7one = Singleton7.Instance; Singleton7 singleton7two = null Class clazz7 = Singleton7.class; //直接打印出构造方法,根据构造方法创建出实例 就不再反编译Singleton7.class类了 Constructor<Singleton7>[] constructor7 = clazz7.getDeclaredConstructors(); for (Constructor<Singleton7> singleton7Constructor : constructor7) { System.out.println(singleton7Constructor); //执行打印结果为: //private com.practice.singleton.type8.Singleton7(java.lang.String,int) //也就是说,构造器类似于:private Singleton7(String,int){} } //通过上一步得到的结果,得到参数为String、int类型的构造方法 Constructor<Singleton7> constructor1 = clazz7.getDeclaredConstructor(String.class,int.class); constructor1.setAccessible(true); singleton7two = constructor1.newInstance("枚举",1); System.out.println("singleton7one=" + singleton7one); System.out.println("singleton7two=" + singleton7two); //打印结果: //java.lang.IllegalArgumentException: Cannot reflectively create enum objects
总结
- 软件开发过程中,当需要单一的对象工作时,我们可以使用单例模式,推荐使用DCL、静态内部类和枚举方式,虽然DCL、静态内部类方式在某种情况下不能保证绝对的单例,但对于系统中的绝大多数情况例如延迟加载、线程安全等都已经满足了,我们可以根据自己的需要选择合适的单例模式实现方式。