zoukankan      html  css  js  c++  java
  • 单例模式1

      单例模式作为设计模式中最常用的设计模式之一,无论是第三方库,还是在我们的日常开发中,几乎都可以看到它。单例模式提供了一种在多线程情况下保证实例唯一性的解决方案。虽然其实现非常简单,但是实现的方式却多种多样。本文会从三个维度对其进行评估:线程安全、高性能、懒加载。

      不过我们先不看其具体代码。我们先从它在jdk中的一个实际应用Runtime这个类说起,有一个直观的感受。

      首先,看一看实现代码,如下:

    public class Runtime {
        private static Runtime currentRuntime = new Runtime();
    
        /**
         * Returns the runtime object associated with the current Java application.
         * Most of the methods of class <code>Runtime</code> are instance
         * methods and must be invoked with respect to the current runtime object.
         *
         * @return  the <code>Runtime</code> object associated with the current
         *          Java application.
         */
        public static Runtime getRuntime() {
            return currentRuntime;
        }
    
        /** Don't let anyone else instantiate this class */
        private Runtime() {}
        
        ...
    }

      JDK API对于这个类的解释,每个Java应用程序都有一个Runtime类的实例,使得应用程序和其运行的环境可以相关联起来,可以通过getRuntime方法获取当前运行时。应用程序不能创建自己的Runtime类实例。

      这段话,概括起来,两点:

      1.每一个应用程序都有一个Runtime实例。

      2.应用程序不能创建自己的Runtime类实例。

      只有一个,不能自己去创建,这不就是典型的单例模式么?好了,到这里之后,具体看下每种写法。

    饿汉式

      写法:

    public class Singleton {
    
        // 在定义实例对象的时候直接初始化
        private static Singleton instance = new Singleton();
    
        // 私有,不允许外部去new
        private Singleton() {
    
        }
    
        public static Singleton getInstance() {
            return instance;
        }
    }

      饿汉式的关键在于instance作为类变量并且直接得到了初始化,我们知道如果主动使用Singleton类,那么instance实例将会直接完成创建。包括其中的实例变量都会得到初始化。instance作为类变量在类初始化的过程中会被收集在<clinit>方法中,该方法能够确保百分之一百同步,也就是说instance在多线程的情况下不可能被实例化两次。但是呢,instance被实例化后可能很长一段时间才被使用,那也就意味着instance实例所占用的堆内存会驻留更久的时间。如果说一个类中的成员变量比较少,且占用的内存资源不多,恶汉的方式也不是不行,但是,如果一个勒种的成员变量都是相对来说比较重的资源,那么这种实现方式的弊端就会显现出来。

      总结来说,饿汉式的单例设计模式可以保证多个线程下的唯一实例,getInstance方法性能也比较高,但是无法进行懒加载,也就是当使用到它时,再去加载它。

    懒汉式

      所谓懒汉式就是在使用类实例的时候再去创建(用时创建),这样就避免了类在初始化时提前创建,懒汉式的示例代码如下:

      

    public class Singleton {
    
        // 定义实例,但是不直接初始化
        private static Singleton instance = null;
        
        private Singleton() {
    
        }
    
        public static Singleton getInstance() {
            if(null == instance) {
                instance = new Singleton();
            }
            return instance;
        }
    }

      简单说一下,Singleton.class在初始化的时候instance并不会被实例化。在getInstance方法中会判断instance实例是否被实例化。这貌似看起来没有任何问题,但是如果仔细想来,把其带入到多线程的环境中去进行分析,instance这个实例会被实例化一次以上,不能保证单例的唯一性。两个线程在其某一个时刻,可能同时看到instance==null。

    懒汉式+同步方法

      上面懒汉式的写法可以保证实例的懒加载,但是无法保证实例的唯一性,在多线程开发中,需要保证对共享资源的同步性。稍加修改,增加同步的约束即可。修改的代码如下:

      

    public class Singleton {
    
        // 定义实例,但是不直接初始化
        private static Singleton instance = null;
    
        private Singleton() {
    
        }
    
        public static synchronized Singleton getInstance() {
            if(null == instance) {
                instance = new Singleton();
            }
            return instance;
        }
    }

      到此,这种方式既满足了懒加载又能够百分百的保证instance实例的唯一性,但是synchronized关键字天生的排他性导致了getInstance方法只能保证在同一时刻被一个线程所访问,很不友好,性能低下。

    Double-Check

      Double-Check是一种比较聪明的设计方式,它提供了一种高效的数据同步策略,那就是首次初始化加载,之后则允许多个线程同时进行getinstance方法的调用来获得类的实例,写法如下:

      

    public class Singleton {
        
        private static Singleton instance = null;
    
        // 资源
        Connection conn;
        
        Socket socket;
        
        private Singleton() {
    //        this.conn; // 初始化conn
    //        this.socket; // 初始化socket 
        }
        
        public static Singleton getInstance() {
            // 当instance是null时,进入同步代码块,同时该判断避免了每次都需要进入同步代码块,可以提高效率
            if (null == instance) {
                // 只有一个线程能够获得Singleton.class关联的monitor
                synchronized (Singleton.class) {
                    // 判断如果instance石null就去创建
                    if (null == instance) {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }

      当两个线程发现null==instance成立时,只有一个线程又资格进入到同步代码块,完成对instance实例化,随后的线程发现null == instance不能立则无需进行任何动作,以后对getInstance的访问就不需要数据同步的保护了。

      这种方式看起来是那么的完美和巧妙,既满足了懒加载,又保证了instance实例的唯一性,Double-Check的方式提供了高效的数据同步策略,可以允许多个线程同时对其进行访问,但是这种方式在多线程的情况下有可能会引起空指针异常。

      分析下原因:

         在Singleton的构造方法中,需要分别实例化conn和sokcet两个资源,还有Singleton自身,根据JVM运行时指令重排序和Happerns-Before规则,这三种之间的实例化顺序并无前后关系的约束,那么极有可能是instance最先被实例化,而conn和socket并未完成实例化,为完成初始化的实例调用其方法将会抛出空指针异常。如下图示:

      

    Volatitle + Double + Check

    Holder 方式

    枚举方式 ... 

    设计模式六大原则

    1、开闭原则OCP

      开闭原则说的是,对扩展开放、对修改关闭。在程序需要进行扩展的时候,不能去修改原有的代码,这也是为了使程序的扩展性更好、易于升级和维护。

    2、里氏代换原则LSP

      在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立。如果一个软件实体使用的是一个子类对象的话,那么它一定不能够使用基类对象。里氏代换原则的程序表现就是:在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类。

    3、控制反转原则IOC

      针对接口编程,依赖于抽象而不依赖于具体

    4、接口隔离原则ISP

      使用多个隔离的接口,比使用单个接口要好

    5、迪米特法则DP

      一个实体应当尽量少地与其他实体间发生相互作用,使得系统功能模块相对独立

    6、合成复用原则

      尽量使用组合/聚合的方式,而不是使用继承

    设计中的三个关键字

    1、抽象化

      在众多事物中提取出共同的、本质性的特征,舍弃非本质的特征,就是抽象化。抽象化的过程也是一个剪裁的过程,在抽象时,同于不同,取决于从什么角度上来抽象。抽象的角度取决于分析问题的目的。

    2、实现化

      抽象类给出的具体实现,就是实现化。

      一个类的实例就是这个类的实例化,一个具体子类是它的抽象超类的实例化。

    3、解耦

      这就比较重要了,平时我们老说好的代码应该是"高内聚、低耦合",那么什么是耦合呢?

      所谓耦合,就是两个实体的行为的某种强关联。而将它们之间的强关联去掉,就是解耦。解耦是指将抽象化和实现化之间的耦合解开,或者说是将它们之间的强关联改换成弱关联。

    所谓强关联,指的是在编译时期已经确定的,无法在运行时期动态改变的关联;所谓弱关联,就是可以动态地确定并且在运行时期动态改变的关联。从这个定义看,继承关系是强关联,聚合关系是弱关联。

      

      

  • 相关阅读:
    编写内核模块
    ubuntu安装虚拟磁带库mhvtl
    linux中断与异常
    jquery 强大表格插件 DataTables
    判断元素是否存时,使用isset会比in_array快得多
    MarkDown 语法
    接口测试、压力测试工具
    jquery 复制文本到剪切板插件(非 flash)
    fiddler抓包HTTPS请求
    php mongodb manager 查数据的各种姿势
  • 原文地址:https://www.cnblogs.com/sunl123/p/11079605.html
Copyright © 2011-2022 走看看