7种Java单例模式

转载至http://blog.csdn.net/u010923921/article/details/45504777


单例模式 - 终极篇

1.  前言

单例(Singleton )是设计模式当中使用比较常用和重要的一种模式,有些 架构 师并不把单例作为一种设计模式,而是一种实现方式。下面是我自己总结的7 中单例模式的写法,废话不多说,直接上代码:(分享注明出处即可,看完这一篇基本上不用再看其他乱起八糟的总结了!)

2. 什么是单例?

单例对象的类必须保证只有一个实例存在(from wiki

懒汉式:lazy load

第一种(懒汉式简单版):

  1. public class Single1 {  
  2.       
  3.     private static Single1 instance;//此处一定是私有  
  4. //  public Single1(){}  省略默认构造  
  5.     public static Single1 getInstance(){  
  6.         if (instance==null) {  
  7.             instance=new Single1();  
  8.         }  
  9.         return instance;  
  10.     }  
  11. }  

很明显这是一种有缺陷的写法,初步优化,是将构造器私有,这样可以防止被外部的类调用。
  1. <span style="font-family:Courier New;">1.   //ViViD    
  2. 2.  public class Singleton {    
  3. 3.      private static Singleton instance = null;    
  4. 4.      
  5. 5.      private Singleton() {    
  6. 6.      }    
  7. 7.      
  8. 8.      public static Singleton getInstance() {    
  9. 9.          if (instance == null) {    
  10. 10.             instance = new Singleton();    
  11. 11.         }    
  12. 12.         return instance;    
  13. 13.     }    
  14. 14. }  </span>  
这种写法在大多数的时候也是没问题的。虽然 具备lazyloding ,但致命缺点:多线程下不能正常工作。 这种写法能够在多线程中很好的工作,但是,遗憾的是,效率很低,99% 情况下不需要同步。 问题在于,当多线程工作的时候, 如果有多个线程同时运行到 if (instance == null) ,都判断为null ,那么两个线程就各自会创建一个实例——这样一来,就不是单例了。

第二种(懒汉,线程安全):

  1. 1.  <span style="font-family:Courier New;">public class Singleton {    
  2. 2.      private static Singleton instance = null;    
  3. 3.      
  4. 4.      private Singleton() {    
  5. 5.      }    
  6. 6.      
  7. 7.      public static Synchronized Singleton getInstance() {    
  8. 8.          if (instance == null) {    
  9. 9.              instance = new Singleton();    
  10. 10.         }    
  11. 11.         return instance;    
  12. 12.     }    
  13. 13. }</span>  
这种写法能够在多线程中很好的工作,而且看起来它也具备很好的lazy loading,但是,遗憾的是,效率很低,99%情况下不需要同步。
加上synchronized关键字之后,getInstance方法就会锁上了。如果有两个线程(T1、T2)同时执行到这个方法时,会有其中一个线程T1获得同步锁,得以继续执行,而另一个线程T2则需要等待,当第T1执行完毕getInstance之后(完成了null判断、对象创建、获得返回值之后),T2线程才会执行执行。——所以这端代码也就避免了 第二种 中,可能出现因为多线程导致多个实例的情况。
但是,这种写法也有一个问题: 给gitInstance 方法加锁,虽然会避免了可能会出现的多个实例问题,但是会强制除T1之外的所有线程等待,实际上会对程序的执行效率造成负面影响。

第三种( Double-Check双重检验锁 )

Version2代码相对于Version1d代码的效率问题,其实是为了解决1%几率的问题,而使用了一个100%出现的防护盾。那有一个优化的思路,就是把100%出现的防护盾,也改为1%的几率出现,使之只出现在可能会导致多个实例出现的地方。 JDK1.5 以后源码里就是这样写的。
——有没有这样的方法呢?当然是有的,改进后的代码Vsersion3如下:

  1. <span style="font-family:Courier New;">1.   // 带双重检测锁的单例    
  2. 2.  public class Singleton {    
  3. 3.      private static Singleton5 instance = null;    
  4. 4.      
  5. 5.      private Singleton() {    
  6. 6.      }    
  7. 7.      
  8. 8.      public static Singleton getInstance() {    
  9. 9.          if (instance == null) {    
  10. 10.             synchronized (Singleton.class) {    
  11. 11.                 if (instance == null) {    
  12. 12.                     instance = new Singleton();    
  13. 13.                 }    
  14. 14.             }    
  15. 15.         }    
  16. 16.         return instance;    
  17. 17.     }    
  18. 18. }  </span>  

这个是第二种方式的升级版,俗称双重检查锁定,详细介绍请查看:JDK源码。
在JDK1.5之后,双重检查锁定才能够正常达到单例效果。
这个版本的代码看起来有点复杂,注意其中有两次if (instance == null)的判断,这个叫做『双重检查Double-Check』。
·        第一个if (instance == null),其实是为了解决Version2中的效率问题,只有instance为null的时候,才进入synchronized的代码段——大大减少了几率。
·        第二个if (instance == null),则是跟Version2一样,是为了防止可能出现多个实例的情况。
—— 这段代码看起来已经很完美了。
—— 当然,只是『看起来』,还是有小概率出现问题的。
这弄清楚为什么这里可能出现问题,首先,我们需要弄清楚几个概念:原子操作、指令重排。

饿汉式:eagerly load

饿汉式单例是指:指全局的单例实例在类装载时构建的实现方式。由于类装载的过程是由类加载器(ClassLoader)来执行的,这个过程也是由JVM来保证同步的,所以这种方式先天就有一个优势——能够免疫许多由多线程引起的问题。

第四种(饿汉,线程不安全):

  1. <span style="font-family:Courier New;">1.   public class Singleton {    
  2. 2.      //2、自定义一个本类对象。    
  3. 3.      private static Singleton1 intance = new Singleton();    
  4. 4.      //1、私有化构造函数。    
  5. 5.      private Singleton() {    
  6. 6.      }    
  7. 7.      //3、定义一个方法返回改对象。让其他程序通过这个方法就可以获取该对象。    
  8. 8.      public static Singleton getInstance() {    
  9. 9.          return intance;    
  10. 10.     }    
  11. 11. }  </span>  

第五种(静态内部类):

  1. <span style="font-family:Courier New;">1.   public class Singleton5 {    
  2. 2.      private Singleton5() {}    
  3. 3.      
  4. 4.      private static class SingletonHolder {    
  5. 5.          private static final Singleton5 INSTANCE = new Singleton7();    
  6. 6.      }    
  7. 7.      
  8. 8.      public static final Singleton5 getInstance() {    
  9. 9.          return SingletonHolder.INSTANCE;    
  10. 10.     }    
  11. 11. }  </span>  

这种方式同样利用了 classloder 的机制来保证初始化 instance 时只有一个线程,它跟第三种和第四种方式不同的是(很细微的差别):第三种和第四种方式是只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了, instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有显示通过调用 getInstance 方法时,才会显示装载 SingletonHolder 类,从而实例化 instance 。想象一下,如果实例化 instance 很消耗资源,我想让他延迟加载,另外一方面,我不希望在 Singleton 类加载时就实例化,因为我不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比第三和第四种方式就显得很合理。

第六种(枚举优雅版):

  1. 1.  public enum Singleton {      
  2. 2.      INSTANCE;      
  3. 3.      public void whateverMethod() {      
  4. 4.      }     
  5. 5.  }      

这是一个枚举类型……连class都不用了,极简。使用时可以直接Singleton.INSTANCE. whateverMethod();由于创建枚举实例的过程是线程安全的,所以这种写法也没有同步的问题。
这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。个人认为由于1.5中才加入enum特性,普及率不够高,在实际工作中,发现被使用的不是很广泛。

作者对这个方法的评价:
这种写法在功能上与共有域方法相近,但是它更简洁,无偿地提供了序列化机制,绝对防止对此实例化,即使是在面对复杂的序列化或者反射攻击的时候。虽然这中方法还没有广泛采用,但是单元素的枚举类型已经成为实现Singleton的最佳方法。
枚举单例这种方法问世一些,许多分析文章都称它是实现单例的最完美方法——写法超级简单,而且又能解决大部分的问题。
不过我个人认为这种方法虽然很优秀,但是它仍然不是完美的——比如,在需要继承的场景,它就不适用了。

第七种终极版 (volatile)

对于Double-Check这种可能出现的问题(当然这种概率已经非常小了,但毕竟还是有的嘛~),解决方案是:只需要给instance的声明加上 volatile 关键字即可,volatile版本如下:
  1. 1.<span style="font-family:Courier New;">   public class Singleton    
  2. 2.  {    
  3. 3.      private volatile static Singleton singleton = null;    
  4. 4.      private Singleton()  {    }    
  5. 5.      public static Singleton getInstance()   {    
  6. 6.          if (singleton== null)  {    
  7. 7.              synchronized (Singleton.class) {    
  8. 8.                  if (singleton== null)  {    
  9. 9.                      singleton= new Singleton();    
  10. 10.                 }    
  11. 11.             }    
  12. 12.         }    
  13. 13.         return singleton;    
  14. 14.     }    
  15. 15. }</span>   

    volatile 关键字的一个作用是禁止 指令重排 ,把instance声明为 volatile 之后,对它的写操作就会有一个 内存屏障 什么是内存屏障? ),这样,在它的赋值完成之前,就不用会调用读操作。
    注意:volatile阻止的不singleton = newSingleton()这句话内部[1-2-3]的指令重排,而是保证了在一个写操作([1-2-3])完成之前,不会调用读操作( if (instance == null) )。
  ——也就彻底防止了Version3中的问题发生。
——好了,现在彻底没什么问题了吧?
……
……
……
好了,别紧张,的确没问题了。大名鼎鼎的 EventBus 中,其入口方法 EventBus.getDefault() 就是用这种方法来实现的


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值