java设计模式之单例模式写法,懒汉,饿汉,双检锁

来源:互联网 发布:深圳淘宝运营助理招聘 编辑:程序博客网 时间:2024/05/30 05:21

关键字: singleton 单例 写法 双锁 线程安全

饿汉式单例类

  1. public class Singleton   

  2. {   

  3.     private Singleton(){   

  4.        

  5.     }   

  6.   

  7.     private static Singleton instance = new Singleton();   

  8.   

  9.     private static Singleton getInstance(){   

  10.         return instance;   

  11.     }   

  12. }  


 饿汉式提前实例化,没有懒汉式中多线程问题,但不管我们是不是调用getInstance()都会存在一个实例在内存中

内部类式单例类

  1. public class Singleton      

  2. {         

  3.         private Singleton(){      

  4.           

  5.     }      

  6.      

  7.     private class SingletonHoledr(){      

  8.         private static Singleton instance = new Singleton();      

  9.     }      

  10.      

  11.     private static Singleton getInstance(){      

  12.         return SingletonHoledr.instance;      

  13.     }      

  14. }   


内部类式中,实现了延迟加载,只有我们调用了getInstance(),才会创建唯一的实例到内存中.并且也解决了懒汉式中多线程的问题.解决的方式是利用了Classloader的特性.

 

懒汉式单例类

  1. public class Singleton      

  2. {         

  3.     private Singleton(){      

  4.      

  5.     }      

  6.      

  7.     private static Singleton instance;      

  8.     public static Singleton getInstance(){      

  9.         if(instance == null){      

  10.             return instance = new Singleton();      

  11.         }else{      

  12.             return instance;      

  13.         }      

  14.     }      

  15. }    

 

在懒汉式中,有线程A和B,当线程A运行到第8行时,跳到线程B,当B也运行到8行时,两个线程的instance都为空,这样就会生成两个实例。解决的办法是同步:

可以同步但是效率不高:

 双检锁写法: 

  1. public class Singleton{   

  2.   private static Singleton instance;    //声明静态的单例对象的变量  

  3.   private Singleton(){}    //私有构造方法   

  4.      

  5.   public static Singleton getSingle(){    //外部通过此方法可以获取对象    

  6.     if(instance== null){      

  7.         synchronized (Singleton.class) {   //保证了同一时间只能只能有一个对象访问此同步块        

  8.             if(instance== null){       

  9.                 instance= new Singleton();           

  10.         }      

  11.       }   

  12.     }     

  13.     return instance;   //返回创建好的对象   

  14.   }   


 
    为什么要在 if 语句中使用两次判断instance== null ,这里涉及到一个名词 Double-Check Locking ,也就是双重检查锁定,为何要使用双重检查锁定呢?

      考虑这样一种情况,就是有两个线程同时到达,即同时调用 GetInstance(),此时由于instance== null ,所以很明显,两个线程都可以通过第一重的instance== null ,进入第一重 if 语句后,由于存在锁机制,所以会有一个线程进入 lock 语句并进入第二重instance== null ,而另外的一个线程则会在 lock 语句的外面等待。而当第一个线程执行完 new  Singleton()语句后,便会退出锁定区域,此时,第二个线程便可以进入 lock 语句块,此时,如果没有第二重instance== null 的话,那么第二个线程还是可以调用 new  Singleton()语句,这样第二个线程也会创建一个 Singleton 实例,这样也还是违背了单例模式的初衷的,所以这里必须要使用双重检查锁定。

       细心的朋友一定会发现,如果我去掉第一重instance== null ,程序还是可以在多线程下完好的运行的,考虑在没有第一重instance== null 的情况下,当有两个线程同时到达,此时,由于 lock 机制的存在,第一个线程会进入 lock 语句块,并且可以顺利执行 new Singleton(),当第一个线程退出 lock 语句块时,instance这个静态变量已不为 null 了,所以当第二个线程进入 lock 时,还是会被第二重instance== null 挡在外面,而无法执行 new Singleton(),所以在没有第一重instance== null 的情况下,也是可以实现单例模式的?那么为什么需要第一重instance== null 呢?这里就涉及一个性能问题了,因为对于单例模式的话,new Singleton()只需要执行一次就 OK 了,而如果没有第一重instance== null 的话,每一次有线程进入 GetInstance()时,均会执行锁定操作来实现线程同步,这是非常耗费性能的,而如果我加上第一重instance== null 的话,那么就只有在第一次,也就是instance ==null 成立时的情况下执行一次锁定以实现线程同步,而以后的话,便只要直接返回 Singleton 实例就 OK 了而根本无需再进入 lock 语句块了,这样就可以解决由线程同步带来的性能问题了。






思路很简单,就是我们只需要同步(synchronize)初始化instance的那部分代码从而使代码既正确又很有效率。
这就是所谓的“双检锁”机制(顾名思义)。
很可惜,这样的写法在很多平台和优化编译器上是错误的。

原因在于:instance = new Singleton()这行代码在不同编译器上的行为是无法预知的。一个优化编译器可以合法地如下实现instance = new Singleton():

1. instance  = 给新的实体分配内存

2. 调用Singleton的构造函数来初始化instance的成员变量

现在想象一下有线程A和B在调用getInstance,线程A先进入,在执行到步骤1的时候被踢出了cpu。然后线程B进入,B看到的是instance  已经不是null了(内存已经分配),
于是它开始放心地使用instance,但这个是错误的,因为在这一时刻,instance的成员变量还都是缺省值,A还没有来得及执行步骤2来完成instance的初始化。

当然编译器也可以这样实现:

1. temp = 分配内存

2. 调用temp的构造函数

3. instance = temp

如果编译器的行为是这样的话我们似乎就没有问题了,但事实却不是那么简单,因为我们无法知道某个编译器具体是怎么做的,因为在Java的memory model里对这个问题没有定义。





双检锁对于基础类型(比如int)适用。很显然吧,因为基础类型没有调用构造函数这一步

0 0
原创粉丝点击