设计模式之单例模式
来源:互联网 发布:淘宝自然堂是正品吗 编辑:程序博客网 时间:2024/06/03 14:51
单例模式
单例模式(Singleton Pattern):确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。单例模式是一种对象创建型模式。
单例模式的应用
优点
- 内存中只有一个实例,减少了内存开销,特别是当一个对象需要频繁地创建、销毁时,单例有很大优势。
- 避免对资源的多重占用,以读写文件为例,单例模式可以避免对同一资源文件的同时写操作。
- 在系统设置全局的访问点、优化和共享资源访问。
缺点
- 单例模式一般没有接口,不便扩展。
- 与单一职责原则有冲突,一个类是否单例应该取决于环境,而不是使用单例模式。
使用场景
- 要求一个类有且只有一个对象,那么可以考虑单例模式。
- 创建对像需要消耗资源过多。
- 需要定义大量的静态常量和静态方法的环境(如工具类)
单例模式的实现
基于枚举的单例
public class EnumSingleton{ private EnumSingleton(){} public static EnumSingleton getInstance(){ return Singleton.INSTANCE.getInstance(); } private static enum Singleton{ INSTANCE; private EnumSingleton singleton; //JVM会保证此方法绝对只调用一次 private Singleton(){ singleton = new EnumSingleton(); } public EnumSingleton getInstance(){ return singleton; } }}
在枚举中我们明确了构造方法限制为私有,在我们访问枚举实例时会执行构造方法,同时每个枚举实例都是static final类型的,也就表明只能被实例化一次。在调用构造方法时,我们的单例被实例化。
也就是说,因为enum中的实例被保证只会被实例化一次,所以我们的INSTANCE也被保证实例化一次。
基于类加载机制的饿汉式
代码实现
class EagerSingleton { private static final EagerSingleton instance = new EagerSingleton(); private EagerSingleton() { } public static EagerSingleton getInstance() { return instance; } }
基于类加载机制的静态内部类实现单例
class Singleton { private Singleton() { } private static class HolderClass { private final static Singleton instance = new Singleton(); } public static Singleton getInstance() { return HolderClass.instance; }}
第一次调用getInstance()
时将加载内部类HolderClass
,在该内部类中定义了一个static
类型的变量instance
,此时会首先初始化这个成员变量,由Java虚拟机来保证其线程安全性,确保该成员变量只能初始化一次。由于getInstance()
方法没有任何线程锁定,因此其性能不会造成任何影响。
懒汉式
class LazySingleton { private static LazySingleton instance = null; private LazySingleton() { } synchronized public static LazySingleton getInstance() { if (instance == null) { instance = new LazySingleton(); } return instance; } }
懒汉式单例在第一次调用getInstance()
方法时实例化,在类加载时并不自行实例化,这种技术又称为延迟加载(Lazy Load)
技术,即需要的时候再加载实例。
线程安全的懒汉式
为了避免多个线程同时调用getInstance()
方法,我们可以使用关键字synchronized
,代码如下所示:
class LazySingleton { private static LazySingleton instance = null; private LazySingleton() { } synchronized public static LazySingleton getInstance() { if (instance == null) { instance = new LazySingleton(); } return instance; } }
节省资源的双重校验锁
懒汉式单例类在getInstance()
方法前面增加了关键字synchronized
进行线程锁,以处理多个线程同时访问的问题。但是,上述代码虽然解决了线程安全问题,但是每次调用getInstance()
时都需要进行线程锁定判断,在多线程高并发访问环境中,将会导致系统性能大大降低。如何既解决线程安全问题又不影响系统性能呢?我们继续对懒汉式单例进行改进。事实上,我们无须对整个getInstance()
方法进行锁定,只需对其中的代码instance = new LazySingleton();
进行锁定即可。因此getInstance()方法可以进行如下改进:
public static LazySingleton getInstance() { if (instance == null) { synchronized (LazySingleton.class) { instance = new LazySingleton(); } } return instance; }
注意,新的问题出现了。假如在某一瞬间线程A和线程B都在调用getInstance()
方法,此时instance
对象为null
值,均能通过instance == null
的判断。由于实现了synchronized
加锁机制,线程A进入synchronized
锁定的代码中执行实例创建代码,线程B处于排队等待状态,必须等待线程A执行完毕后才可以进入synchronized
锁定代码。但当A执行完毕时,线程B并不知道实例已经创建,将继续创建新的实例,导致产生多个单例对象,违背单例模式的设计思想,因此需要进行进一步改进,在synchronized
中再进行一次(instance == null)
判断,这种方式称为双重检查锁定(Double-Check Locking)。使用双重检查锁定实现的懒汉式单例类完整代码如下所示:
class LazySingleton { private volatile static LazySingleton instance = null; private LazySingleton() { } public static LazySingleton getInstance() { //第一重判断 if (instance == null) { //锁定代码块 synchronized (LazySingleton.class) { //第二重判断 if (instance == null) { instance = new LazySingleton(); //创建单例实例 } } } return instance; } }
值得一提的是此处的单例对象 instance 必须使用
volatile
修饰。声明成 volatile 的变量被认为是顺序一致的,即,不是重新排序的。
关于双重校验锁的分析
public static Singleton getInstance(){ if (instance == null) { synchronized(Singleton.class) { //1 if (instance == null) //2 instance = new Singleton(); //3 } } return instance;}
双重检查锁定背后的理论是:在 //2 处的第二次检查使创建两个不同的 Singleton 对象成为不可能。
假设有下列事件序列:
- 线程 1 进入 getInstance() 方法。
- 由于 instance 为 null ,线程 1 在 //1 处进入 synchronized 块。
- 线程 1 被线程 2 预占。
- 线程 2 进入 getInstance() 方法。
- 由于 instance 仍旧为 null ,线程 2 试图获取 //1 处的锁。然而,由于线程 1 持有该锁,线程 2 在 //1 处阻塞。
- 线程 2 被线程 1 预占。
- 线程 1 执行,由于在 //2 处实例仍旧为 null ,线程 1 还创建一个 Singleton 对象并将其引用赋值给 instance 。
- 线程 1 退出 synchronized 块并从 getInstance() 方法返回实例。
- 线程 1 被线程 2 预占。
- 线程 2 获取 //1 处的锁并检查 instance 是否为 null 。
- 由于 instance 是非 null 的,并没有创建第二个 Singleton 对象,由线程 1 创建的对象被返回。
双重检查锁定背后的理论是完美的。不幸地是,现实完全不同。双重检查锁定的问题是:并不能保证它会在单处理器或多处理器计算机上顺利运行。双重检查锁定失败的问题并不归咎于 JVM 中的实现 bug,而是归咎于 Java 平台内存模型。内存模型允许所谓的“重排序”,是失败的一个主要原因。
假设代码执行以下事件序列:
- 线程 1 进入 getInstance() 方法。
- 由于 instance 为 null ,线程 1 在 //1 处进入 synchronized 块。
- 线程 1 前进到 //3 处,但在构造函数执行之前 ,使实例成为非 null 。
- 线程 1 被线程 2 预占。
- 线程 2 检查实例是否为 null 。因为实例不为 null,线程 2 将 instance 引用返回给一个构造完整但部分初始化了的 Singleton 对象。
- 线程 2 被线程 1 预占。
- 线程 1 通过运行 Singleton 对象的构造函数并将引用返回给它,来完成对该对象的初始化。
此事件序列发生在线程 2 返回一个尚未执行构造函数的对象的时候。为展示此事件的发生情况,假设为代码行 ·instance =new Singleton();`执行了下列伪代码:
mem = allocate(); //Allocate memory for Singleton object.instance = mem; //Note that instance is now non-null, but has not been initialized.ctorSingleton(instance); //Invoke constructor for Singleton passing instance.
- 设计模式之 单例设计模式
- 设计模式之 单例设计模式
- 设计模式之单例设计模式
- 设计模式之-----------单例设计模式
- 设计模式之:单例设计模式
- 设计模式之单例设计模式
- 设计模式之单例设计模式
- 设计模式之单例设计模式
- 设计模式之单例设计模式
- 设计模式之单例设计模式
- 设计模式之单例设计模式
- 设计模式之单例设计模式
- 设计模式之单例设计模式
- 设计模式之-单例设计模式
- 设计模式之单例设计模式 标签: 设计模式
- 设计模式之单例
- 设计模式之单例
- 设计模式之 单例
- Java绑定
- Handle/Body pattern(Wrapper pattern)
- 头文件在.c/.cpp中引用和.h文件引用
- 高斯消元模板
- POJ
- 设计模式之单例模式
- 排序的最好和最坏的时间复杂度问题
- GPG key retrieval failed: [Errno 14] Could not open/read file:///etc/pki/rpm-gpg/RPM-GPG-KEY-mysql
- Linux常用基本命令
- 面向对象六大原则解析
- 慕课笔记--[课程]webpack深入与实战
- Java clone方法浅析
- Vincent-Starry, Starry Night
- mysql 5.6.35--libc.so.6(GLIBC_2.14)(64bit),libstdc++.so.6(GLIBCXX_3.4.15)(64bit)