单例模式中的多线程分析

谈到单例模式,我们立马会想到饿汉式和懒汉式加载,所谓饿汉式就是在创建类时就创建好了实例,懒汉式在获取实例时才去创建实例,即延迟加载。

饿汉式:

Java代码   收藏代码
  1. <strong>package com.bijian.study;  
  2.   
  3. public class Singleton {  
  4.   
  5.     private Singleton() {  
  6.     }  
  7.   
  8.     // 注意这是private 只供内部调用  
  9.     private static Singleton instance = new Singleton();  
  10.   
  11.     // 这里提供了一个供外部访问本class的静态方法,可以直接访问    
  12.     public static Singleton getInstance() {  
  13.         return instance;  
  14.     }  
  15. }  
  16. </strong>  

懒汉式:

Java代码   收藏代码
  1. <strong>package com.bijian.study;  
  2.   
  3. public class Singleton {  
  4.   
  5.     private static Singleton instance = null;  
  6.   
  7.     public static synchronized Singleton getInstance() {  
  8.   
  9.         // 这个方法比上面有所改进,不用每次都进行生成对象,只是第一次       
  10.         // 使用时生成实例,提高了效率!  
  11.         if (instance == null)  
  12.             instance = new Singleton();  
  13.         return instance;  
  14.     }  
  15. }  
  16. </strong>  

     上面第二中形式的lazy initialization,也就是说第一次调用时初始Singleton,以后就不用再生成了。

注意到lazy initialization形式中的synchronized,这个synchronized很重要,如果没有synchronized,那么使用getInstance()是有可能得到多个Singleton实例。一般认为第一种形式要更加安全些。

       对于上面的懒汉式方式,从多线程角度来看,Synchronized放到方法上会影响性能。于是我们不难想到将其放到方法里。

Java代码   收藏代码
  1. package com.bijian.study;  
  2.   
  3. public class Singleton {  
  4.   
  5.     private static Singleton instance = null;  
  6.     private static String lock = new String();  
  7.   
  8.     public static Singleton getInstance() {  
  9.   
  10.         // 这个方法比上面有所改进,不用每次都进行生成对象,只是第一次       
  11.         // 使用时生成实例,提高了效率!  
  12.         if (instance == null)  
  13.             synchronized(lock) {  
  14.                 instance = new Singleton();  
  15.             }  
  16.         return instance;  
  17.     }  
  18. }  

我们稍加分析,不难发现,这样会存在线程安全问题,假如线程一刚好执行完if (instance == null)的判断语句(还未加锁),调度至线程二也执行这条判断语句,也是true,也进入了if的语句块中,这样就会产生两个实例,而非单实例了。

此时,我们稍加分析,那还不容易,在锁里面再加一个是否为空的判断,即所谓的double-checked locking (DCL),如下所示:

Java代码   收藏代码
  1. package com.bijian.study;  
  2.   
  3. public class Singleton {  
  4.   
  5.     private static Singleton instance = null;  
  6.     private static String lock = new String();  
  7.   
  8.     public static Singleton getInstance() {  
  9.   
  10.         // 这个方法比上面有所改进,不用每次都进行生成对象,只是第一次       
  11.         // 使用时生成实例,提高了效率!  
  12.         if (instance == null)  
  13.             synchronized(lock) {  
  14.                 if (instance == null) {  
  15.                     instance = new Singleton();  
  16.                 }  
  17.             }  
  18.         return instance;  
  19.     }  
  20. }   

     此时,很多人都会觉得无懈可击了。从多线程角度来看,这样写的单例确实没有问题了,但从Java的类创建原理来看,可能还有问题。从浅显简单的理解来看,就是对象还未完全创建出来,但instance变量已被赋值,此时另一个线程获取实例时,会得到instance,但它的堆空间及相关的方法还未完成时,调用实例方法就会出错。

       啊?还存在这样的问题呀?这可以虚拟机的实现问题,难道还要我考虑?是的,其实稍加改动,就可以避免这样的问题。

       解决办法,加一个局部变量,如下所示:

Java代码   收藏代码
  1. package com.bijian.study;  
  2.   
  3. public class Singleton {  
  4.   
  5.     private static Singleton instance = null;  
  6.     private static String lock = new String();  
  7.   
  8.     public static Singleton getInstance() {  
  9.   
  10.         // 这个方法比上面有所改进,不用每次都进行生成对象,只是第一次       
  11.         // 使用时生成实例,提高了效率!  
  12.         if (instance == null)  
  13.             synchronized(lock) {  
  14.                 if (instance == null) {  
  15.                     Singleton temp = new Singleton();  
  16.                     instance = temp;  
  17.                 }  
  18.             }  
  19.         return instance;  
  20.     }  
  21. }  

 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值