设计模式之单例模式
一、介绍
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
TIPS: 1、单例类只能有一个实例。 2、单例类必须自己创建自己的唯一实例。 3、单例类必须给所有其他对象提供这一实例。
实现的目标:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
解决得问题:一个全局使用的类频繁地创建与销毁。
核心思想:构造函数是私有的。并且对外提供一个全局的访问方法。
使用场景:
1、要求生产唯一序列号。 2、代码级别的全局的JVM缓存。(有状态的单例) 3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
二、懒汉式
所谓的懒汉式单例,是指:第一次调用才初始化,避免内存浪费。典型的时间换取空间的做法。因为第一次获取单例的时候会执行单例的实例化,浪费一些时间。但是后面再继续获取就不存时间问题了。
2.1、懒汉式的简化版
1 package com.shf.mode.singleton; 2 3 /** 4 * 描述:懒汉式 5 * 单例实例在第一次调用的时候创建 6 * 典型的以时间换取空间,第一次调用 getInstance()才会实例化单例。 7 * 8 * @Author shf 9 * @Date 2019/7/15 16:12 10 * @Version V1.0 11 **/ 12 public class SingletonExample2 { 13 // 私有构造方法,禁止外部直接创建实例 14 private SingletonExample2(){} 15 // 私有 单例对象 将对象的管理权封闭 16 private static SingletonExample2 instance = null; 17 18 // 静态的工厂方法 19 public static SingletonExample2 getInstance(){ 20 if(instance == null){ 21 instance = new SingletonExample2(); 22 } 23 return instance; 24 } 25 }
ok,我们看一下这种最简单的懒汉式存在的问题。
在多线程环境下,当两个线程同时访问这个方法,同时制定到instance==null的判断。都判断为null,接下来同时执行new操作。这样类的构造函数被执行了两次。一旦构造函数中涉及到某些资源的处理,那么就会发生错误。所以说最简式是线程不安全的。
2.2、懒汉式 synchronized版
1 public class SingletonExample2 { 2 // 私有构造方法,禁止外部直接创建实例 3 private SingletonExample2(){} 4 // 私有 单例对象 将对象的管理权封闭 5 private static SingletonExample2 instance = null; 6 7 // 静态的工厂方法 8 public synchronized static SingletonExample2 getInstance(){ 9 if(instance == null){ 10 instance = new SingletonExample2(); 11 } 12 return instance; 13 } 14 }
如上代码所示,我们采用synchronized修饰getInstance()方法。有效的解决了线程安全性的问题,但是,,,但是synchronized直接锁死了getInstance()方法,意味着在多线程环境下getInstance()方法的并发效率会大大降低,性能损耗十分严重,因为getInstance()方法已经成为了一个同步方法。
2.3、懒汉式的双重检测机制
1 public class SingletonExample2 { 2 // 私有构造方法,禁止外部直接创建实例 3 private SingletonExample2(){} 4 // 私有 单例对象 将对象的管理权封闭 5 private static SingletonExample2 instance = null; 6 7 // 静态的工厂方法 8 public static SingletonExample2 getInstance(){ 9 if(instance == null){// 双重监测机制 10 synchronized(SingletonExample2.class){// 同步锁 11 if(instance == null){ 12 instance = new SingletonExample2(); 13 } 14 } 15 } 16 return instance; 17 } 18 }
双重检测机制保证了 synchronized 代码块中代码的线程安全性,另外当已经初始化完成了单例后调用 getInstance()方法,不会再次进入synchronized 代码块中,貌似是满足了线程安全性和性能的提升。但是,,,真的是线程安全的吗?NO,因为 instance = new SingletonExample2(); 该行代码会发生指令重排。
在上述代码中,执行new操作的时候,CPU一共进行了三次指令
1,memory = allocate() 分配对象的内存空间 2,ctorInstance() 初始化对象 3,instance = memory 设置instance指向刚分配的内存
在程序运行过程中,CPU在不违背 happens-before 八大原则的前提下,为提高运算速度会做出违背代码原有顺序的优化。我们称之为乱序执行优化或者说是指令重排。那么上面知识点中的三步指令极有可能被优化为1,3,2的顺序。当我们有两个线程A与B,A线程遵从132的顺序,经过了两此instance的空值判断后,执行了new操作,并且cpu在某一瞬间刚结束指令3,并且还没有执行指令2。而在此时线程B恰巧在进行第一次的instance空值判断,由于线程A执行完3指令,为instance分配了内存,线程B判断instance不为空,直接执行return,返回了instance,这样就出现了错误。
2.4、懒汉式终极版:双重检测机制+volatile禁止指令重排
1 package com.shf.mode.singleton; 2 3 /** 4 * 描述:懒汉式 5 * 单例实例在第一次调用的时候创建 6 * 典型的以时间换取空间,第一次调用 getInstance()才会实例化单例。 7 * 8 * @Author shf 9 * @Date 2019/7/15 16:12 10 * @Version V1.0 11 **/ 12 public class SingletonExample2 { 13 // 私有构造方法,禁止外部直接创建实例 14 private SingletonExample2(){} 15 // 1、memory = allocate() 分配对象的内存空间 16 // 2、ctorInstance() 初始化对象 17 // 3、instance = memory 设置instance指向刚分配的内存 18 19 // 单例对象 volatile + 双重检测机制 -> 禁止指令重排 20 // 私有 单例对象 将对象的管理权封闭 21 private volatile static SingletonExample2 instance = null; 22 23 // 静态的工厂方法 24 public static SingletonExample2 getInstance(){ 25 if(instance == null){// 双重监测机制 26 synchronized(SingletonExample2.class){// 同步锁 27 if(instance == null){ 28 instance = new SingletonExample2(); 29 } 30 } 31 } 32 return instance; 33 } 34 }
关于happens-before原则和volatile禁止指令重排感兴趣的可以度娘一下。或者参考一下这篇博客。
双重检测机制又称双重校验锁(DCL,即 double-checked locking)。面试官很中意这个玩意哦。
三、饿汉式
所谓饿汉式就是不管你用不用,我都先给你创建出来。空间换时间,但是毕竟如果不用的话岂不是白白浪费了空间,如果保证这个单例一定会被调用,饿汉式也是一个不错的选择,而且实现的难度相对于懒汉式也简单不少。
1 package com.shf.mode.singleton; 2 3 /** 4 * 描述:饿汉式 5 * 单例实例在类装载时创建 6 * 典型的用空间换取时间----不管用不用都先创建出来 7 * 8 * @Author shf 9 * @Date 2019/7/15 15:58 10 * @Version V1.0 11 **/ 12 public class SingletonExample1 { 13 // 私有构造方法,禁止外部直接创建实例 14 private SingletonExample1(){} 15 // 私有 单例对象 将对象的管理权封闭 16 private static SingletonExample1 instance = null; 17 // 静态方法块 完成单例的实例化 18 static { 19 instance = new SingletonExample1(); 20 } 21 // public 对外开发的获取单例的唯一渠道 22 public SingletonExample1 getInstance(){ 23 return instance; 24 } 25 }
TIPS:
静态域和静态代码块的顺序不要乱了哦,静态域与静态块是按照顺序执行的。这是Java基础知识的内容,就不过多解释了。
四、枚举式
这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化,这个是JVM保证的。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。
1 package com.shf.mode.singleton; 2 3 /** 4 * 描述: 5 * 6 * @Author shf 7 * @Date 2019/7/16 0:28 8 * @Version V1.0 9 **/ 10 public class SingletonExample3 { 11 // 私有构造方法,禁止外部直接创建实例 12 private SingletonExample3() {} 13 14 public static SingletonExample3 getInstance() { 15 return Singleton.INSTANCE.getInstance(); 16 } 17 18 private enum Singleton { 19 INSTANCE; 20 private SingletonExample3 singleton; 21 22 Singleton() { 23 singleton = new SingletonExample3(); 24 } 25 26 public SingletonExample3 getInstance() { 27 return singleton; 28 } 29 } 30 }
如有错误的地方还请留言指正。
原创不易,转载请注明原文地址:https://www.cnblogs.com/hello-shf/p/11192500.html