定义

  确保只有一个类只有一个实例,并提供全局访问点。

为什么要使用它

  对一些类来说保证只有一个实例是很重要的。比如windows操作系统中的资源管理器,回收站等工具必须保证只有一个实例,否则系统将会出现一些意想不到的异常。

优点

  因为只有一个实例,所以很容易控制它的访问权限;避免了过多的使用静态变量等。

适用范围

  当类只能有一个实例而且客户可以从一个众所周知的访问点访问它。

结构(UML)

  单例模式的结构比较简单,只有一个类:它的构造器是私有的并且提供了一个返回一个实例的静态方法。

  单例模式的UML比较简单:

  

实现

  假设现在你有一个地方需要使用到单例模式,你可能首先会想到这样写:

 1 package com.tony.singleton;
 2 
 3 /**
 4  * 1、私有化构造器
 5  * 2、提供一个返回实例的方法(全局访问点)
 6  * 这种方法叫做懒汉式
 7  */
 8 public class Singleton01 {
 9     private static Singleton01 instance = null;
10     //私有化构造器
11     private Singleton01(){
12     }
13     //静态工厂方法
14     public static Singleton01 getInstance(){
15         if(instance == null){
16             instance = new Singleton01();
17         }
18         return instance;
19     }
20 }

  但是这种方法又有一个问题:现在的程序一般都是多线程的,在并发的情况下可能会出现两个实例!

  让我们来分析一下这种情况:现在有两个线程(A、B)同时调用了getInstance()方法,然后一个A线程发现instance为null。当A线程正准备去new一个实例时,B线程也发现instance为null,所以B线程也去new一个实例。这种情况下就会出现两个实例。

  怎么解决呢?

  此时我们会想这还不简单,给它加一个synchronized同步锁不就OK了?!

 1 package com.tony.singleton;
 2 
 3 /**
 4  * 1、私有化构造器
 5  * 2、提供一个返回实例的方法(全局访问点)
 6  */
 7 public class Singleton02 {
 8     private static Singleton02 instance = null;
 9     //私有化构造器
10     private Singleton02(){
11     }
12     //静态工厂方法
13     public static synchronized Singleton02 getInstance(){
14         if(instance == null){
15             instance = new Singleton02();
16         }
17         return instance;
18     }
19 }

  OK问题解决了!同步这种方法简单易行解决了线程的并发问题,但同时又带来了一个新的问题:对性能的影响。如果getInstance()频繁被调用,那么你就得重新考虑了:每次调用之前都要给它加同步锁!你要知道同步一个方法可能造成程序执行效率下降100倍,而且只有实例化这个对象时才需要同步。

  好吧,既然麻烦都在实例化对象这里,那么我在类加载器加载的时候我就把这个对象实例化了是不是就可以呢?

 1 package com.tony.singleton;
 2 
 3 /**
 4  * 
 5  * 这种方式就做    饿汉式
 6  */
 7 public class Singleton03 {
 8     //当被类加载器加载的时候就把这个类给实例化
 9     private static Singleton03 instance = new Singleton03();
10     //私有化构造器
11     private Singleton03(){
12     }
13     //静态工厂方法
14     public static  Singleton03 getInstance(){
15         return instance;
16     }
17 }

  利用这种做法,我们依赖JVM在加载这个类时马上创建此唯一的实例。JVM保证在任何线程访问instance变量之前,一定先创建此实例。

  这种做法很好,基本上解决了上面的出现的两个问题:1、并发访问;2、对性能的影响。

  这种做法适合不太复杂的实例。如果需要实例化的的类很复杂,在创建和运行时方面的负担太重就会增加JVM的负担。

  有没有这样的方法:除了解决最初的那两个问题外还能延时加载,减轻JVM的负担?答案是有!

  这种方法叫做双重检测锁。这次引进了一个新东西:volatile 关键字。

 1 package com.tony.singleton;
 2 
 3 /**
 4  * 双重检测锁
 5  * 
 6  */
 7 public class Singleton04 {
 8     //增加volatile关键字!!!
 9     private volatile static Singleton04 instance = null;
10     //私有化构造器
11     private Singleton04(){
12     }
13     //静态工厂方法
14     public static synchronized Singleton04 getInstance(){
15         if(instance == null){
16             //这段代码仅有一次执行的机会:只有第一次才彻底执行
17             synchronized(Singleton04.class){
18                 if(instance == null){
19                     instance = new Singleton04();
20                 }
21             }
22         }
23         return instance;
24     }
25 }

  这时候synchronized所同步的那段代码只会执行一次,而且还保证了延时加载。

总结

  实现单例模式的方法还有几种,但比较常用的就是这几种。一般掌握饿汉式、懒汉式和双重检测锁就可以了。

  这几种各有优缺点,具体使用那种还有具体情况具体分析。

  懒汉式:能够延时加载,但可能对性能影响较大。

  懒汉式:对性能影响较小,但不能延时加载。

  双重检测锁:能够延时加载,只需同步一次,但是不支持JDK1.4之前的版本。

  

  其他设计模式:设计模式专栏

 

参考文献

 

  《Head First 设计模式》

 

  《设计模式》