zoukankan      html  css  js  c++  java
  • 设计模式系列之(1):单例模式


    1 介绍:
        单例模式是非常简单、非常基础的一种创建型设计模式。
    单例模式是在Java开发(不论是android开发,还是Java服务器端开发)中经常使用的一个设计模式。由于在Java服务器端开发中经常使用,所以单例模式的实现还涉及到了Java并发编程。在java面试中,常常被要求使用java实现单例模式,因此有必要熟练掌握。本文是自己学习java单例模式的一个总结,参考了网上的很多资料,大部分内容不是原创。在这里要向参考文献的作者表示感谢。
        本文组织结构如下: 2 介绍单例模式的java实现到底有多少种;3-9分别介绍这些实现方式; 10 提出一些进一步的问题; 11 总结本文。


    2 到底有多少种实现?
        "【深入】java 单例模式"一文列出了5种实现方式,一种为GOF的、线程不安全的实现方式,剩下4种是线程安全(thread-safe)的实现方式,分别为:同步方法(synchronized function)方式、双重检查加锁(DCL,double-checked  locking)方式、急切加载方式 和内部类方式。

        "Java:单例模式的七种写法"一文列出了7种实现方式:线程不安全方式、同步方法方式、急切加载方式(及其变种)、静态内部类方式、枚举方式和双重校验锁方式。 但急切加载方式及其变种应该统一看做是一种实现。

        所以本文的结论就是: java单例模式一共有下面将要介绍的5种实现方式。

     



    4. 第一种: 线程不安全方式(GOF方式经典方式) ( 懒加载)

    这是GOF和HeadFirstDesignPattern中介绍的最经典的一种方式。这种方式在单线程情况下可以实现懒加载,但在多线程情况下会出问题。

     1 public class Creation_singleton {        
     2   // private static instance, which is default null        
     3   private static Creation_singleton instance;
     4   // private constructor        
     5   private Creation_singleton () {
     6   }                
     7   // public get instance function        
     8   public static Creation_singleton getInstance ( ) {
     9     if ( instance == null ) {         
    10       instance = new Creation_singleton();        
    11     }          
    12     return instance;       
    13   }  
    14 }


    5. 第二种:简单加锁方式 ( synchronized )

    由第一种而来,很自然会想到,保证线程安全的方式可以是使用synchronized关键字。这种方式是线程安全,又是懒加载。但使用synchronized 会降低性能。

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

    我们必须避免使用synchronization,因为它很慢。----上述论断在一定程度上已经不是事实了,因为在现代JVM上,如果是无竞争的同步的话,synchronized并不会很慢了。但是当在多线程环境中,多个线程同时竞争getInstance方法时,还是可能会导致性能下降。

    之所以说上述论断不是事实,是与显式锁进行比较得出的结论。内置锁一度被认为比Lock显式锁的性能低很多,但随着JVM的优化,synchronized关键字的性能已经不比Lock低很多了。


     
    6. 急切加载方式 ( 变种 )

     1 class Singleton {         
     2   //私有,静态的类自身实例    
     3   private static Singleton instance = new Singleton();         
     4   //私有的构造子(构造器,构造函数,构造方法)    
     5   private Singleton(){}         
     6   //公开,静态的工厂方法     
     7   public static Singleton getInstance() {
     8         return instance;    
     9   }
    10 }

     这种方式有一种变种,就是使用静态初始化块来初始化单例。

     有人说这种方式有个缺点:其实跟全局变量是一样的: 不管该实例是不是到了实际被使用的时刻,也不管应用程序最终是不是使用该单例,在类加载 时都会创建该单例,所以叫“急切创建”, 但是这样一来,单例模式的延迟创建的优点就没有了。 所以该版本与全局变量的方式并没有区别(?yes) static 成员变量只有在类加载的时候初始化一次。类加载是线程安全的。 所以该方法实现的单例是线程安全的。 (static的全局变量也是线程安全的)

     但是jvm的类加载也是懒加载的,并不是一开始启动jvm的时候就全部加载所有类。加载这个类,跟创建这个类的实例,二者的时机,在大部分时候,应该是同时的,也就是说,应该不存在很多跟创建这个类的实例无关的、需要加载这个类的情况。正是由于这个原因,这种方式,在NTS代码中使用的很多。


    7. 双重检查加锁(dcl)方式
    在生产环境中可以看到如下代码,是双重检查加锁的实现。双重检查加锁是简单加锁方式的一种“自作聪明”的改进,其实这种方式在多线程环境下会失败。

     1    private static SocketFactory instance  = null;
     2 
     3    private static SocketFactory getInstance()
     4    {
     5       if (instance == null) 
     6       {
     7          synchronized (SocketFactory.class)
     8          {
     9             if (instance == null)
    10             {
    11                instance = new SocketFactory();
    12             }
    13          }
    14       }
    15       return instance;
    16    }

    双重检查加锁方式也可以正确地被实现,前提是对上述双重检查加锁方式做出一定的改进。这也让双重检查加锁方式成为了最复杂的实现方式。因此这种方式是不被推荐的。 双重检查加锁方式的正确实现由三种:volatile方式、 final方式、 temp instance方式。因为temp instance 方式非常的繁琐,在本文中就不再做介绍了,有兴趣的读者可以去参考文献中找答案。

    7.1. 使用DCL+volatile

    在Java5中,DCL实际上是可行的,如果你给instance域加上volatile修饰符。例如,如果我们需要给getInstance()方法传递一个database connection的话,那么以下代码是可行的:

     1 public class MyFactory {
     2   private static volatile MyFactory instance;
     3 
     4   public static MyFactory getInstance(Connection conn)
     5        throws IOException {
     6     if (instance == null) {
     7       synchronized (MyFactory.class) {
     8         if (instance == null)
     9           instance = new MyFactory(conn);
    10       }
    11     }
    12     return instance;  
    13   }
    14 
    15   private MyFactory(Connection conn) throws IOException {
    16     // init factory using the database connection passed in
    17   }
    18 }

    但是需要注意的是,上述代码仅仅在Java5及其以上版本中才可以,因为Java5对volatiel关键字的语义进行了修正。(Java4及其以前版本中Volatile关键字的语义都不是完全正确的)。当使用Java5获取一个volatile变量时,获取行为具有synchronization同步语义。换句话说,Java5保证了如下的Happen-before规则:对volatile变量的未同步的读操作必须在写操作之后才能发生,从而读线程将会看到MyFactory对象的所有域的正确值。

    7.2 把instance域声明为final

    从java 5 之后才有的新特性之一是,final 域的语义有了新的变化。这些域的值是在构造函数中被赋值的,JVM会确保这些值在这个对象引用自己之前被提交到主内存。

    换句话说,如果其他线程可以看到这个对象,那么它们永远不可能看到这个对象的final域的未经初始化的值。所以在这种情况下,我们将不需要把这个instance的引用声明为volatile。

    (问题:是否需要把所有域都声明为final? )


    8. 静态嵌套类方式

    1 public class Creation_singleton_innerClass {          
    2   private static class SingletonHolder{         
    3     private static Creation_singleton_innerClass instance = new Creation_singleton_innerClass();     
    4     }          
    5   private Creation_singleton_innerClass() {              }          
    6   public static Creation_singleton_innerClass getInstance() {         
    7     return SingletonHolder.instance;     
    8   } 
    9 }    



     该方式与急切创建方式有些类似。 java机制规定,内部类SingletonHolder只有在getInstance()方法第一次调用的时候才会被加载(实现了lazy), 说明内部类的加载时机跟外部类的加载时机不同。 而且其加载过程是线程安全的(实现线程安全)。内部类加载的时候实例化一次instance,以后不会再初始化。
     该实现方式可以有一些小变化:instance可以加一个final修饰;静态嵌套类可以改为内部类。

    9. 枚举方式
     

    1 enum Singleton_enum {    
    2   INSTANCE;
    3   void method () {
    4   }
    5 }



    该方式是《effective java》中推荐的方法。 枚举类型是在java1.5中引入的,enum可以看做是一种特殊的class,除了不能继承。INSTANCE是Singleton_enum类的实例。
     
    上述代码会被JVM编译为:

    1 final class MySingleton {     
    2   final static MySingleton INSTANCE = new MySingleton();      
    3   void method () {     }
    4 }

    虽然enum的域是编译时常量,但他们是对应enum类型的实例,他们仅在对应enum类型首次被引用的时候被生成。当代码首次运行到访问INSTANCE处时,类MyStingleton才会被JVM装载和初始化。该装载和初始化过程只初始化static域一次。

    10 进一步问题:

    我在一次面试中,被问到如下问题:单例模式与static方法的区别是什么? 当时没回答上来,现在把答案整理如下:

    10.1 单例模式与 static方法的简单比较
    static 方法无法实现单例;

    如果类与其他类的交互比较复杂,容易造成一些跟初始化有关的、很难调试的bug;
    static是急切初始化。
    static方法最大的不同就是不能作为参数传递给别的方法。单例可以把这个单例object作为参数传递给其他方法,并当做普通对象来使用。
    一个静态类只允许静态方法。

    11 参考文献:
    http://www.cnblogs.com/coffee/archive/2011/12/05/inside-java-singleton.html
    http://www.blogjava.net/kenzhh/archive/2015/04/06/357824.html
    http://www.raychase.net/257
    https://en.wikipedia.org/wiki/Singleton_pattern

  • 相关阅读:
    DigitalOcean上使用Tornado+MongoDB+Nginx+Supervisor+DnsPod快速搭建个人博客
    创业三年来的一些感想
    创业三年来的一些感想
    ViEmuVS2013-3.2.1 破解
    瘋子C语言笔记(指针篇)
    瘋子C语言笔记(结构体/共用体/枚举篇)
    瘋子C++笔记
    petalinux add pre-build application to rootfs compile faliure solution
    QA Issue: No GNU_HASH in the elf binary
    视觉SLAM——特征点法与直接法对比以及主流开源方案对比 ORB LSD SVO DSO
  • 原文地址:https://www.cnblogs.com/hzg1981/p/4993845.html
Copyright © 2011-2022 走看看