设计模式六大原则之单例模式
前言:不断学习就是程序员的宿命
一、单例模式
所谓的单例设计模式,就是采取一定的方法保证整个的软件系统中,对某个类只能存在一个对象实例,并且该类只能提供一个取得对象实例的方法(静态方法)。比如Hibernate的SessionFactory,它充当数据存储源的代理,并负责创建session对象。SessionFactory并不是轻量级的
单例模式有以下8种方式:
二、饿汉式(静态常量)
public class Singleton01 { //本类内部创建对象实例 private final static Singleton01 instance=new Singleton01(); //构造器私有化,外部不能new private Singleton01(){} //提供一个公有静态方法,返回实例对象 private static Singleton01 getInstance(){ return instance; } public static void main(String[] args) { //测试 Singleton01 instance1 = Singleton01.getInstance(); Singleton01 instance2 = Singleton01.getInstance(); System.out.println(instance1==instance2); System.out.println(instance1.hashCode()); System.out.println(instance1.hashCode()); } }
分析:
(1)优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
(2)缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费
三、饿汉式(静态代码块)
public class Singleton02 { //本类内部创建对象实例 private static Singleton02 instance; //构造器私有化, 外部不能new private Singleton02(){} // 在静态代码块中,创建单例对象 static { instance = new Singleton02(); } //提供一个公有的静态方法,返回实例对象 public static Singleton02 getInstance() { return instance; } public static void main(String[] args) { //测试 Singleton02 instance = Singleton02.getInstance(); Singleton02 instance2 = Singleton02.getInstance(); System.out.println(instance == instance2); // true System.out.println("instance.hashCode=" + instance.hashCode()); System.out.println("instance2.hashCode=" + instance2.hashCode()); } }
分析:
(1)这种方式和上述方式类似,只不过将类实例化的过程放在了静态代码块中,也就是在装载的时候执行静态代码中的代码,初始化类的实例
(2)结论:单例方式可用,但可能会造成内存浪费
四、懒汉式(线程不安全)
/** * @ClassName: Singleton03 * @Description: 懒汉式-线程不安全 * @Author: xiedong * @Date: 2020/4/5 0:19 */ public class Singleton03 { private static Singleton03 instance; private Singleton03() {} //提供一个静态的公有方法,当使用到该方法时,才去创建 instance public static Singleton03 getInstance() { if(instance == null) { instance = new Singleton03(); } return instance; } public static void main(String[] args) { System.out.println("懒汉式1 , 线程不安全~"); Singleton03 instance = Singleton03.getInstance(); Singleton03 instance2 = Singleton03.getInstance(); System.out.println(instance == instance2); // true System.out.println("instance.hashCode=" + instance.hashCode()); System.out.println("instance2.hashCode=" + instance2.hashCode()); } }
分析:
(1)起到了Lazy Loading的效果,但是只能在单线程下使用
(2)如果在多线程下,一个线程进入if(instance==null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例,所以在多线程环境下不可使用这种方式。
(3)结论:实际开发中,不要使用这种方式
五、懒汉式(同步方法保证线程安全)
/** * @ClassName: Singleton04 * @Description: 懒汉式-同步方法 * @Author: xiedong * @Date: 2020/4/5 0:53 */ public class Singleton04 { private static Singleton04 instance; private Singleton04() {} //提供一个静态的公有方法,加入同步处理的代码,解决线程安全问题 //即懒汉式 public static synchronized Singleton04 getInstance() { if(instance == null) { instance = new Singleton04(); } return instance; } public static void main(String[] args) { System.out.println("懒汉式2 , 线程安全~"); Singleton04 instance = Singleton04.getInstance(); Singleton04 instance2 = Singleton04.getInstance(); System.out.println(instance == instance2); // true System.out.println("instance.hashCode=" + instance.hashCode()); System.out.println("instance2.hashCode=" + instance2.hashCode()); } }
分析:
(1)解决线程安全问题
(2)效率太低了,每个线程在想获得类的实例的时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想要获得该类实例,直接return就行了,方法同步效率太低了。
(3)结论:在实际开发中,不推荐使用这种方式
六、懒汉式(同步代码块保证线程安全)
/** * @ClassName: Singleton05 * @Description: 懒汉式-同步代码块 * @Author: xiedong * @Date: 2020/4/5 0:56 */ public class Singleton05 { private static Singleton05 instance; private Singleton05() { } public static Singleton05 getInstance() { if (instance == null) { synchronized (Singleton05.class) { instance = new Singleton05(); } } return instance; } public static void main(String[] args) { Singleton05 instance = Singleton05.getInstance(); Singleton05 instance2 = Singleton05.getInstance(); System.out.println(instance == instance2); // true System.out.println("instance.hashCode=" + instance.hashCode()); System.out.println("instance2.hashCode=" + instance2.hashCode()); } }
分析:
(1)这种方式,本意是想对上述(同步方法)实现方式的改进,因为前面同步方法效率太低了,改为同步产生实例化的代码块
(2)但是这种同步并不能起到线程同步的作用。假如一个线程进入了if(instance==null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。
(3)结论:实际开发中,不能使用这种方式
七、双重检查
/** * @ClassName: Singleton06 * @Description: 双重检查 * @Author: xiedong * @Date: 2020/4/5 1:07 */ public class Singleton06 { private static volatile Singleton06 instance; private Singleton06() {} //提供一个静态的公有方法,加入双重检查代码,解决线程安全问题, 同时解决懒加载问题 //同时保证了效率, 推荐使用 public static synchronized Singleton06 getInstance() { if(instance == null) { synchronized (Singleton06.class) { if(instance == null) { instance = new Singleton06(); } } } return instance; } public static void main(String[] args) { System.out.println("双重检查"); Singleton06 instance = Singleton06.getInstance(); Singleton06 instance2 = Singleton06.getInstance(); System.out.println(instance == instance2); // true System.out.println("instance.hashCode=" + instance.hashCode()); System.out.println("instance2.hashCode=" + instance2.hashCode()); } }
分析:
(1)Double-Check概念是多线程开发中常使用到的,如代码中所示,两次if(singleton==null)检查,这样就保证线程安全了。
(2)这样实例化代码只用执行一次,后面再次访问到时,判断if(singleton==null)直接return实例对象,也避免了反复进行方法同步。
(3)线程安全;延迟加载;效率较高
(4)结论:在实际开发中,推荐使用这种单例设计模式
八、静态内部类
/** * @ClassName: Singleton7 * @Description: * @Author: xiedong * @Date: 2020/4/5 1:20 */ public class Singleton7 { private static volatile Singleton7 instance; //构造器私有化 private Singleton7() {} //写一个静态内部类,该类中有一个静态属性 Singleton private static class SingletonInstance { private static final Singleton7 INSTANCE = new Singleton7(); } //提供一个静态的公有方法,直接返回SingletonInstance.INSTANCE public static synchronized Singleton7 getInstance() { return SingletonInstance.INSTANCE; } public static void main(String[] args) { System.out.println("使用静态内部类完成单例模式"); Singleton7 instance = Singleton7.getInstance(); Singleton7 instance2 = Singleton7.getInstance(); System.out.println(instance == instance2); // true System.out.println("instance.hashCode=" + instance.hashCode()); System.out.println("instance2.hashCode=" + instance2.hashCode()); } }
分析:
(1)这种方式采用了类装载的机制来保证初始化实例时只有一个线程
(2)静态内部类方式在Singleton07类(外部类)被装载时并不会立即实例化,而是在需要实例化时,调用getInstance()方法,才会装载SingletonInstance类,从而完成Singleton的实例化
(3)类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
(4)优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高
(5)结论:推荐使用
九、枚举
/** * @ClassName: Singleton08 * @Description: * @Author: xiedong * @Date: 2020/4/5 1:30 */ public class Singleton08 { 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.sayOK(); } } //使用枚举,可以实现单例, 推荐 enum Singleton { INSTANCE; //属性 public void sayOK() { System.out.println("ok~"); } }
分析:
(1)这里借助JDK1.5中添加的枚举来实现单例模式。不仅能够避免多线程同步问题,而且还能防止反序列化重新创建新的对象
(2)这种方式是Effective Java作者Josh Bloch提倡的方式
(3)结论:推荐使用
十、单例模式在JDK应用举例
可以发现,JDK中使用饿汉式-静态属性
十一、总结
(1)单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
(2)当想实例化一个单例类的时候,必须要记住使用相应获取对象的方法,而不是new
(3)单例模式的使用场景:需要频繁的创建和销毁对象、创建对象时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)