2设计模式-单例模式
2018-02-27 16:18
92 查看
单例模式
定义:
确保某个类只有一个实例,能自行实例化并向整个系统提供这个实例。所谓单例,指的就是单实例,有且仅有一个类实例,这个单例不应该由人来控制,而应该由代码来限制,强制单例。
使用场景:
当产生多个对象会消耗过多资源,比如IO和数据操作某种类型的对象只应该有且只有一个,如,在多个功能模块里都需要写的log
一般是对于那些业务逻辑上限定不能多例只能单例的情况,例如:类似于计数器之类的存在,一般都需要使用一个实例来进行记录,若多例计数则会不准确。
实现方式
1、懒汉式,线程不安全
是否 Lazy 初始化:是。是否多线程安全:否
实现难度:易
描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。
这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。
public class SimpleSingleton { //1.static单例变量 private static SimpleSingleton instance; //2.私有的构造方法 private SimpleSingleton() { } //3.静态方法为调用者提供单例对象 public static SimpleSingleton getInstance() { if (instance == null) {//1 instance = new SimpleSingleton();//2 } return instance; } }
在多线程高并发的情况下,这样写会有明显的问题,当线程A调用getInstance方法,执行到1时,检测到instance为null,于是执行2去实例化instance,当2没有执行完时,线程B又调用了getInstance方法,这时候检测到instance依然为空,所以线程B也会执行2去创建一个新的实例。这时候,线程A和线程B得到的instance就不是一个了,这违反了单例的定义。
2、饿汉式
是否 Lazy 初始化:否是否多线程安全:是
实现难度:易
描述:这种方式比较常用,但容易产生垃圾对象。
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
它基于 classloder 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。
public class EHanSingleton { //static final单例对象,类加载的时候就初始化 private static final EHanSingleton instance = new EHanSingleton(); //私有构造方法,使得外界不能直接new private EHanSingleton() { } //公有静态方法,对外提供获取单例接口 public static EHanSingleton getInstance() { return instance; } }
饿汉单例模式解决了多线程并发的问题,因为在加载这个类的时候,就实例化了instance。当getInstatnce方法被调用时,得到的永远是类加载时初始化的对象(反序列化的情况除外)。但这也带来了另一个问题,如果有大量的类都采用了饿汉单例模式,那么在类加载的阶段,会初始化很多暂时还没有用到的对象,这样肯定会浪费内存,影响性能,我们还是要倾向于1的做法,在首次调用getInstance方法时才初始化instance。
3、懒汉式,线程安全
是否 Lazy 初始化:是是否多线程安全:是
实现难度:易
描述:这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。
优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。
getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)
/** * 懒汉模式 * @author josan_tang */ public class LanHanSingleton { private static LanHanSingleton instance; private LanHanSingleton() { } /** * 增加synchronized关键字,该方法为同步方法,保证多线程单例对象唯一 */ public static synchronized LanHanSingleton getInstance() { if (instance == null) { instance = new LanHanSingleton(); } return instance; } }
与1的唯一区别在于getInstance方法前加了synchronized 关键字,让getInstance方法成为同步方法,这样就保证了当getInstance第一次被调用,即instance被实例化时,别的调用不会进入到该方法,保证了多线程中单例对象的唯一性。
- 优点:单例对象在第一次调用才被实例化,有效节省内存,并保证了线程安全。
- 缺点:同步是针对方法的,以后每次调用getInstance时(就算intance已经被实例化了),也会进行同步,造成了不必要的同步开销。==不推荐使用这种方式。==
4、双检锁/双重校验锁(DCL,即 double-checked locking)
JDK 版本:JDK1.5 起是否 Lazy 初始化:是
是否多线程安全:是
实现难度:较复杂
描述:这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
getInstance() 的性能对应用程序很关键。
public class DCLSingleton { //增加volatile关键字,确保实例化instance时,编译成汇编指令的执行顺序 private volatile static DCLSingleton instance; private DCLS 4000 ingleton() { } public static DCLSingleton getInstance() { if (instance == null) { synchronized (DCLSingleton.class) { //当第一次调用getInstance方法时,即instance为空时,同步操作,保证多线程实例唯一 //当以后调用getInstance方法时,即instance不为空时,不进入同步代码块,减少了不必要的同步开销 if (instance == null) { instance = new DCLSingleton(); } } } return instance; } }
DCL失效:
在JDK1.5之前,可能会有DCL实现的问题,上述代码中的20行,在Java里虽然是一句代码,但它并不是一个真正的原子操作。
instance = new DCLSingleton();
它编译成最终的汇编指令,会有下面3个阶段:
给DCLSingleton实例分配内存
调用DCLSingleton的构造函数,初始化成员变量。
将instance指向分配的内存空间(这个操作以后,instance才不为null)
在jdk1.5之前,上述的2、3步骤不能保证顺序,也就是说有可能是1-2-3,也有可能是1-3-2。如果是1-3-2,当线程A执行完步骤3(instance已经不为null),但是还没执行完2,线程B又调用了getInstance方法,这时候线程B所得到的就是线程A没有执行步骤2(没有执行完构造函数)的instance,线程B在使用这样的instance时,就有可能会出错。这就是DCL失效。
在jdk1.5之后,可以使用volatile关键字,保证汇编指令的执行顺序,虽然会影响性能,但是和程序的正确性比起来,可以忽悠不计。
Java内存模型
优点:第一次执行getInstance时instance才被实例化,节省内存;多线程情况下,基本安全;并且在instance实例化以后,再次调用getInstance时,不会有同步消耗。
缺点:jdk1.5以下,有可能DCL失效;Java内存模型影响导致失效;jdk1.5以后,使用volatile关键字,虽然能解决DCL失效问题,但是会影响部分性能。
5、登记式/静态内部类
是否 Lazy 初始化:是是否多线程安全:是
实现难度:一般
描述:这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
==推荐使用==
public class StaticClassSingleton { //私有的构造方法,防止new private StaticClassSingleton() { } public static StaticClassSingleton getInstance() { return StaticClassSingletonHolder.instance; } /** * 静态内部类 */ private static class StaticClassSingletonHolder { //第一次加载内部类的时候,实例化单例对象 private static final StaticClassSingleton instance = new StaticClassSingleton(); } }
第一次加载StaticClassSingleton类时,并不会实例化instance,只有第一次调用getInstance方法时,Java虚拟机才会去加载StaticClassSingletonHolder类,继而实例化instance,这样延时实例化instance,节省了内存,并且也是线程安全的。==这是推荐使用的一种单例模式。==
相关文章推荐
- java设计模式之十桥接模式(Bridge)
- java设计模式基础-单例模式的几种写法
- 设计模式-创建模式
- 设计模式之观察者模式
- java开发常见设计模式
- 【设计模式】之 Factory Method 模式
- .NET常用设计模式——概述
- 设计模式中类的关系
- 23种设计模式(二)原型模式
- (四十六)设计模式初涉
- C++设计模式
- 设计模式之 composite
- 设计模式之代理模式学习------强制代理-----------《设计模式之禅》学习笔记
- .NET设计模式(11):组合模式(Composite Pattern)
- java设计模式_单例
- PHP设计模式(1)工厂模式
- 设计模式C++之八(Adapter适配器模式)
- C#面向对象模式设计第十八讲:Iterator 迭代器模式(行为型模式)
- 设计模式六大原则(4):接口隔离原则
- State(状态)设计模式