• 单例模式


    为什么需要设计模式

    软件开发中经常遇到需求变化的情况,为了应对这种情况,设计出更加易于维护(修改)、更灵活的代码,前人根据开发经验总结了一些准则,根据这些准则可以设计出易维护、更灵活、可复用的代码,这些准则就称为设计模式。设计模式的目的就是设计出高内聚、低耦合的代码。

    单例模式

    单例模式是最简单的设计模式,单例模式:确保类只有一个实例,并且类本身负责其实例化并提供一个全局访问点。

    适用场景

    1)有些类只能有一个实例,例如任务管理器、对话框、ID生成器、总统。

    2)有些对象的创建会耗费大量资源,为了节省资源只创建一个可复用的对象,对那些需要频繁创建和销毁的对象尤其节省资源。

    优点

    1)由于只有一个实例,可以节省系统资源。

    2)提供了对唯一实例的受控访问,即只能通过全局访问点对实例进行访问。

    3)可以扩展实现可变数目的实例。

    缺点

    1)与单一职责有冲突(创建类实例和提供实例放在同一个方法中实现)。

    2)不易扩展。单例模式没有提供抽象层,扩展很困难,只能通过直接修改代码实现扩展。

    实现方式

    单例模式的传统实现方式有饿汉模式和懒汉模式,这两种实现包含同样的关键点:

    1)将构造器声明为私有类型,防止在类外实例化对象,只能在类内部实例化对象。

    2)提供一个私有的静态成员变量。

    3)提供一个公有的静态成员方法作为全局访问点。

    饿汉模式

    在类装载时创建单例的方法称为饿汉模式。饿汉模式的方法是在声明实例对象时直接调用构造器将其初始化,实现代码如下:

    1 public class Singleton{
    2 private static Singleton instance=new Singleton();
    3 private Singleton(){};
    4 public static Singleton getInstance(){
    5 return instance;
    6 }
    7 }
    View Code

    优点

    实现简单且线程安全

    缺点

    1)如果实例占用较多资源,过早创建实例(未使用实例)会导致资源的浪费。

    2)如果实例依赖于参数或者配置文件,这种方式就不适用。

    懒汉模式

    单例在第一次使用时创建的方法称为懒汉模式。实现代码如下:

     1 public class Singleton{
     2 private static Singleton instance;
     3 private Singleton(){};
     4 public static Singleton getInstance(){
     5 if(instance==null){
     6 instance=new Singleton();
     7 }
     8 return instance;
     9 }
    10 }
    View Code

    优点

    1)解决了饿汉模式中无法创建依赖于参数或者配置文件的单例的情形。

    2)在使用时才创建单例,避免了资源的浪费。

    缺点

    在多线程环境下并不合适,可能会生成多个对象实例。

    懒汉模式,线程安全

    通过关键字synchronized将getInstance声明为同步方法,当有线程A在访问这个方法时,其他要访问此方法的线程等待A访问完成后在访问此方法,这样就实现了线程安全的懒汉模式,实现代码如下:

     1 public class Singleton{
     2 private static Singleton instance;
     3 private Singleton(){};
     4 public static synchronized Singleton getInstance(){
     5 if(instance==null){
     6 instance=new Singleton();
     7 }
     8 return instance;
     9 }
    10 }
    View Code

    优点

    解决了懒汉模式的线程不安全问题

    缺点

    同步带来的是性能下降,并且懒汉模式下只需要在初次创建单例的时候同步getInstance方法,之后使用时完全不必同步。

    双重检验锁

    将同步的关键字synchronized改写到getInstance方法内部,如下:

    1 public static Singleton getInstance() {
    2     if (instance == null) {                         
    3         synchronized (Singleton.class) {           
    4                 instance = new Singleton();
    5             }
    6         }    
    7     return instance ;
    8 }

    初始时instance为null,多个线程在“第2行"处均为真,虽然使用了同步关键字,不过仍然会串行的产生多个实例,因此需要再添加一个检测,如下:

    public static Singleton getInstance() {
        if (instance == null) {                         //Single Checked
            synchronized (Singleton.class) {
                if (instance == null) {                 //Double Checked
                    instance = new Singleton();
                }
            }
        }
        return instance ;
    }

    添加了两个检测之后,还是不能保证线程安全,因为instance=new Singleton()并不是原子操作,包含四个步骤:

    1)为instance创建内存(instance为null)

    2)在堆上开辟内存用于存储Singleton对象。

    3)调用Singleton的构造函数初始化成员变量,形成实例

    4)将instance指向Singleton实例所在(此时instance非null)

    由于JVM的指令重排序,可能导致执行顺序为1-2-4-3而非1-2-3-4,则在线程A执行到4时,未执行3之前,被线程B抢占,由于此时instance!=null,所以会直接返回instance,不过此时instance指向的对象实例并未初始化,导致错误。通过使用关键字volatile修饰instance关闭指令重排序,使instance=new Singleton()按照原有顺序执行。完整代码如下:

     1 public class Singleton {
     2     private volatile static Singleton instance; //声明成 volatile
     3     private Singleton (){}
     4     public static Singleton getInstance() {
     5         if (instance == null) {                        //first check 
     6             synchronized (Singleton.class) {
     7                 if (instance == null) {               //second check              
     8                     instance = new Singleton();
     9                 }
    10             }
    11         }
    12         return instance;
    13     }
    14    
    15 }
    View Code

    枚举实现

    public enum Singleton{
       INSTANCE;
    }
    View Code

    参考:

    1)http://coolshell.cn/articles/265.html

    2)http://wuchong.me/blog/2014/08/28/how-to-correctly-write-singleton-pattern/

    3)http://aleung.github.io/blog/2012/09/17/Java-volatile-/

    4)http://www.importnew.com/20720.html

    5)http://design-patterns.readthedocs.io/zh_CN/latest/creational_patterns/singleton.html#id10

  • 相关阅读:
    java中金钱计算BigDecimal
    SpringBoot的学习二:整合Redis,JPA,Mybatis
    SpringBoot的学习一:入门篇
    Java基础回顾一
    golang 实现冒泡排序
    Go统计键盘输入随机字母的个数
    破解点触码的识别之第三方平台超级鹰的SDK(python3版本)
    RuntimeError: Failed to init API, possibly an invalid tessdata path: E:python36报错
    Django项目部署
    Django REST framework 的功能
  • 原文地址:https://www.cnblogs.com/lz3018/p/5936146.html
Copyright © 2020-2023  润新知