设计模式系列之(1):单例模式


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


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

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

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

 java



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

这是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.htmlhttp://www.blogjava.net/kenzhh/archive/2015/04/06/357824.htmlhttp://www.raychase.net/257https://en.wikipedia.org/wiki/Singleton_pattern

相关文章
相关标签/搜索