java常见设计模式之---单例模式

java常见设计模式之---单例模式

  • 1、单例模式简介
    • 应用场景举例
  • 2、单例模式的特点
  • 3、单例模式和静态类
  • 4、单例模式的经典实现
    • 饿汉式单例(典型实现)
      • 饿汉式-静态代码块
    • 懒汉式单例创建,五种方法
      • 0)、懒汉式(典型实现)
      • 1)、同步延迟加载 — synchronized方法
      • 2)、双重检验锁模式(double checked locking pattern)
        • 双重检验锁-volatile关键字防止重排序
      • 3)、静态内部类
      • 4)、lock机制
      • 5)、枚举法

1、单例模式简介

单例模式(Singleton Pattern)是一个比较简单的模式,其原始定义如下:Ensure a class has only one instance, and provide a global point of access to it. 即确保只有一个实例,而且自行实例化并向整个系统提供这个实例。

因程序需要,有时我们只需要某个类只保留一个对象,不希望有更多对象,此时,我们则应考虑单例模式的设计。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。事实上,这些应用都或多或少具有资源管理器的功能。

总之,选择单例模式就是为了避免不一致状态。

应用场景举例

1)、spring中的单例模式:在spring中每个bean都是单例的,这样做的好处是Spring容器可以管理这些bean的生命周期,由spring容器来决定实例的创建、销毁、销毁后的处理等等问题。如果采用非单例模式,则Bean初始化后的管理交给J2EE容器了,Spring容器就不在跟踪管理Bean的生命周期了。

2)、多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。。

3)、操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。

4)、应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加

5)、Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源

2、单例模式的特点

包括下面几个部分的简单记忆”3125

3和1一起记忆,即三个一

  • 单例模式只能有一个实例,
  • 单例类必须创建自己的唯一实例
  • 单例类必须向其他对象提供这一实例

综上所述,单例模式就是为确保一个类只有一个实例,并为整个系统提供一个全局访问点的一种方法。

2即是指2种实现方式:懒汉式和 饿汉式
5即是指五种经典实现

有两种场景可能导致非单例的情况

场景一:如果单例由不同的类加载器加载,那便有可能存在多个单例类的实例

场景二:如果 Singleton 实现了 java.io.Serializable 接口,那么这个类的实例就可能被序列化和反序列化。

3、单例模式和静态类

静态类也可以实现一个类只有一个对象,这又和单例模式有什么区别呢?

  1. 单例可以继承和被继承,方法可以被重写(override),但是静态方法不行。
  2. 静态方法中产生的对象会在执行后被释放,进而被GC所清理,不会一直存在于内存中。
  3. 静态类会在第一次运行时初始化(饿汉式也这样),单例模式可以有其他选择,如立即加载(饿汉式)和延迟加载(懒汉式)
  4. 基于上面两条,单例模式往往存在于DAO层,如果反复的初始化和释放会占用很多系统资源,而使用单例模式将其加载于内存中可以节省资源开销。
  5. 单例模式容易测试

立即加载 : 在类加载初始化的时候就主动创建实例;
延迟加载 : 等到真正使用的时候才去创建实例,不用时不去主动创建。

静态类和单例模式情景的选择:

情景一:不需要维持任何状态,仅仅用于全局访问,此时更适合使用静态类。

情景二:需要维持一些特定的状态,此时更适合使用单例模式。

4、单例模式的经典实现

饿汉式单例(典型实现)

想想“三个一”特点
饿汉式单例实现的方式如下,这种方法是线程安全的,因为单例类被加载时,就会实例化一个对象并交给自己的引用,供系统使用;而且,由于这个类在整个生命周期中只会被加载一次,因此只会创建一个实例,即能够充分保证单例。

//饿汉式单例实现public class Singleton1 {    private Singleton1(){}    private static Singleton1 singleton1 = new Singleton1();    public static Singleton1 getInstance (){        return singleton1;    }}

**缺点:**因为是立即加载的方式创建单例,所以在单例无用的情况下回造成资源的浪费,大量饿汉式单例会给系统造成极大的开销。

饿汉式-静态代码块

public class Singleton {   private Singleton instance = null;   private Singleton() {}// 初始化顺序:基静态、子静态 -> 基实例代码块、基构造 -> 子实例代码块、子构造   static {       instance = new Singleton();  }   public static Singleton getInstance() {       return this.instance;  }}

类初始化时实例化 instance

懒汉式单例创建,五种方法

想想“3125”中的二和五

0)、懒汉式(典型实现)

//不完美的饿汉式单例public class Singleton2 {    private Singleton2(){}    private static Singleton2 instance = null;    public static Singleton2 getInstance (){        if (instance == null){            instance = new Singleton2();        }        return instance ;    }}

为什么说上面的饿汉式单例不完美呢,在单线程情况下,这种方法是线程安全的,但假设在多线程情况下
多个线程会同时执行到if (instance == null),此时尚未有线程创建出instance = new Singleton2();,这时就会有多个线程进入到if判断语句中,创建出多个对象。

这里介绍个知识点,在下面也有用到:

当我们写了 new 操作,JVM 到底会发生什么?
首先,我们要明白的是,new Singleton()是一个非原子操作,代码行instance = new Singleton2();的执行过程可以分为下面三步:
(1)、memory = allocate(); //先在内存开辟空间
(2)、ctorInstance(memory); //初始化对象
(3)、instance = memory; //使instance 指向刚分配的内存地址(执行完这步 instance 才是非Null的)

要留意的是:这个过程可能发生无序写入(指令重排序),也就是说上面的3行指令可能会被重排序导致先执行第3行后执行第2行,也就是说其真实执行顺序可能是下面这种:
(1)、memory = allocate(); //先在内存开辟空间
(2)、instance = memory; //使instance 指向刚分配的内存地址
(3)、ctorInstance(memory); //初始化对象

1)、同步延迟加载 — synchronized方法

public class Singleton3 {    private Singleton3(){}    private static Singleton3 instance = null;    public static synchronized Singleton3 getInstance (){        if (instance == null){            instance = new Singleton3();        }        return instance ;    }}

这种方法确实做到了线程安全,但是在每一个线程执行到getInstance()方法时,只有一个线程获得锁,其他线程需要等待。这样就会因为等待锁资源造成系统性能的下降。

上面传统懒汉式单例的实现唯一的差别就在于:是否使用 synchronized 修饰 getSingleton3()方法。若使用,就保证了对临界资源的同步互斥访问,也就保证了单例。

2)、双重检验锁模式(double checked locking pattern)

对上面的方法进行改进

进行两次 instance == null? 的判断,如果为true才能进入同步代码块,而在同步代码块中再检测一次是因为可能会有多个线程一起进入同步块外的 if,如果在同步块内不进行二次检验的话就有可能会生成多个实例了。

//同步延迟加载 — synchronized块public class Singleton4 {    private Singleton4(){}    private static Singleton4 instance= null;    public static Singleton4 getInstance(){    if (instance == null){        // 使用 synchronized 块,临界资源的同步互斥访问        synchronized (Singleton4.class){                if (instance == null){                    instance = new Singleton4();                }            }        }        return instance;    }}

为什么说使用synchronized块是线程不安全的呢,这里就需要用到前面提到的前置知识了。

如果intence = new Singleton4();的执行顺序是1-2-3
即:先分配内存空间,再初始化成员变量,最后分配引用地址。这样出来的instance 是非null的。

但如果执行顺序是1-3-2的情况下
即:先分配内存空间,在分配引用地址,最后再初始化变量。

如果是后者,则在 3 执行完毕、2 未执行之前,此时另外有一个线程进行第一个判断,发现instance!=null,返回instance(但其实instance还没有完成初始化),使用会导致报错,当然发生错误的概率极低,但是这个错误我们是可以规避的。

只需要将 instance 变量声明成 volatile 就可以了。

双重检验锁-volatile关键字防止重排序

public class Singleton4 {    private Singleton4(){}    //给instance加上了volatile关键字    private volatile static Singleton4 instance= null;    public static Singleton4 getInstance(){            if (instance == null){            // 使用 synchronized 块,临界资源的同步互斥访问            synchronized (Singleton4.class){                if (instance == null){                    instance = new Singleton4();                }            }        }        return instance;    }}

使用 volatile 的主要原因是其另一个特性:禁止指令重排序优化。也就是说,在 volatile 变量的赋值操作后面会有一个内存屏障(生成的汇编代码上),读操作不会被重排序到内存屏障之前。比如上面的例子,取操作必须在执行完 1-2-3 之后或者 1-3-2 之后,不存在执行到 1-3 然后取到值的情况。从「先行发生原则」的角度理解的话,就是对于一个 volatile 变量的写操作都先行发生于后面对这个变量的读操作(这里的“后面”是时间上的先后顺序)。

但是特别注意
在 Java 5 以前的版本使用了 volatile 的双检锁还是有问题的。其原因是 Java 5 以前的 JMM (Java 内存模型)是存在缺陷的,即时将变量声明成 volatile 也不能完全避免重排序,主要是 volatile 变量前后的代码仍然存在重排序问题。这个 volatile 屏蔽重排序的问题在 Java 5 中才得以修复,所以在这之后才可以放心使用 volatile。

3)、静态内部类

public class Singleton {  //静态内部类里面创建了一个Singleton单例   private static class InstanceHolder {        private static final Singleton INSTANCE = new Singleton();    }     private Singleton (){}     public static final Singleton getInstance() {        return InstanceHolder.INSTANCE;    }  }  

这种写法仍然使用JVM本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。

4)、lock机制

// 类似双重校验锁写法public class Singleton {      private static Singleton instance = null;      private static Lock lock = new ReentrantLock();      private Singleton() {}      public static Singleton getInstance() {          if(instance == null) {              lock.lock(); // 显式调用,手动加锁              if(instance == null) {                  instance = new Singleton();              }              lock.unlock(); // 显式调用,手动解锁          }          return instance;      }}

5)、枚举法

public enum singleton5 {INSTANCE;private  InnerClass _instance;private singleton5() {_instance = new InnerClass();}public InnerClass getInstance() {return _instance;}private class InnerClass{}}

主要是枚举类型是值类型的数据,所以不可能获取到构造函数,也就无法通过构造函数来得到新的实例。这样可以实现很安全的单例模式。(目前很多使用枚举实现单例模式)

缺点:
枚举类型会造成更多的内存消耗。枚举会比使用静态变量多消耗两倍的内存,如果是Android应用,尽量避免。

特点:
(1)线程安全(枚举类型默认就是安全的)

(2)避免反序列化破坏单例

来源:https://www.icode9.com/content-1-801901.html

(0)

相关推荐

  • 单例模式的八种写法

    单例模式作为日常开发中最常用的设计模式之一,是最基础的设计模式,也是最需要熟练掌握的设计模式.单例模式的定义是:保证一个类仅有一个实例,并提供一个访问它的全局访问点.那么你知道单例模式有多少种实现方式 ...

  • Singleton单例模式

    Singleton单例模式

  • 浅谈C++设计模式--单例模式

    单例模式(Singleton)[分类:创建型] [目的:为程序的某个类的访问,提供唯一对象实例] 这估计是设计模式里面最简单的一个类了,下面我们一起看看,单例它是什么?以及如何实现一个单例 基本定义 ...

  • 单例模式的实现

    记一下学习单例模式的笔记: 单例就是要保证该类仅有一个实例.实现完全封闭的单例(外部不能new)其实就要两点要求: 全局访问:需要一个该类型的全局静态变量,每次获取实例时都要判断它是否null,不存在 ...

  • 设计模式(2) 单例模式

    单例模式 线程安全的Singleton 会破坏Singleton的情况 线程级Singleton 单例模式是几个创建型模式中最独立的一个,它的主要目标不是根据客户程序调用生成一个新的实例,而是控制某个 ...

  • Java设计模式之单例模式

    单例模式,是特别常见的一种设计模式,因此我们有必要对它的概念和几种常见的写法非常了解,而且这也是面试中常问的知识点. 所谓单例模式,就是所有的请求都用一个对象来处理,如我们常用的Spring默认就是单 ...

  • 设计模式之单例模式(Singleton Pattern)

    一.定义 一个类只有一个实例,且该类能自行创建这个实例的一种模式. 二.单例模式举例 例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各 ...

  • PHP设计模式之单例模式

    PHP设计模式之单例模式 单例模式绝对是在常用以及面试常问设计模式中排名首位的.一方面它够简单,三言两语就能说明白.另一方面,它又够复杂,它的实现不仅仅只有一种形式,而且在Java等异步语言中还要考虑 ...

  • [PHP小课堂]PHP设计模式之单例模式

    [PHP小课堂]PHP设计模式之单例模式 关注公众号:[硬核项目经理]获取最新文章 添加微信/QQ好友:[DarkMatterZyCoder/149844827]免费得PHP.项目管理学习资料

  • Java基础之:单例模式

    设计模式 设计模式是在大量的实践中总结和理论化之后优选的代码结构.编程风格.以及解决问题的思考方式 .设计模式就像是经典的棋谱,不同的棋局,我们用不同的棋谱,免去我们自己再思考和摸索. 单例模式 对于 ...

  • 【设计模式】单例模式(Singleton Pattern)

    懒汉式 public class Singleton { private static Singleton instance; private Singleton() {}; public stati ...

  • 23种设计模式入门 -- 单例模式

    单例模式:采用一定的方法,使得软件运行中,对于某个类只能存在一个实例对象,并且该类只能提供一个取得实例的方法. 分类: 饿汉式 静态常量方式 静态代码块方式 懒汉式 普通方式,线程不安全 同步方法方式 ...

  • 设计模式之单例模式

    目录: 什么是单例模式 单例模式的应用场景 单例模式的优缺点 单例模式的实现 总借 一.什么是单例模式 单例模式顾名思义就是只存在一个实例,也就是系统代码中只需要一个对象的实例应用到全局代码中,有点类 ...