zoukankan      html  css  js  c++  java
  • 设计模式之单例模式

    一、介绍

      单例模式(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

  • 相关阅读:
    JS ipad图片拖动缩放 PHP
    PHP 格式化输出 PHP
    PHP Smarty模版使用 PHP
    PHP PDO小试 PHP
    PHP 获取网页所有连接 PHP
    C# 生成不重复随机字符串 (1秒内生成1000000个) PHP
    使用VS 开发PHP PHP
    PHP 扑捉未处理异常 PHP
    PHP 格式化MYSQL返回float类型 PHP
    CheckBox 选中判断及实现单选功能
  • 原文地址:https://www.cnblogs.com/hello-shf/p/11192500.html
Copyright © 2011-2022 走看看