确保对象的惟一性——单例模式 (三)

饿汉式单例与懒汉式单例的讨论

      Sunny公司开发人员使用单例模式实现了负载均衡器的设计,可是在实际使用中出现了一个很是严重的问题,当负载均衡器在启动过程当中用户再次启动该负载均衡器时,系统无任何异常,但当客户端提交请求时出现请求分发失败,经过仔细分析发现原来系统中仍是存在多个负载均衡器对象,致使分发时目标服务器不一致,从而产生冲突。为何会这样呢?Sunny公司开发人员百思不得其解。html

      如今咱们对负载均衡器的实现代码进行再次分析,当第一次调用getLoadBalancer()方法建立并启动负载均衡器时,instance对象为null值,所以系统将执行代码instance= new LoadBalancer(),在此过程当中,因为要对LoadBalancer进行大量初始化工做,须要一段时间来建立LoadBalancer对象。而在此时,若是再一次调用getLoadBalancer()方法(一般发生在多线程环境中),因为instance还没有建立成功,仍为null值,判断条件(instance== null)为真值,所以代码instance= new LoadBalancer()将再次执行,致使最终建立了多个instance对象,这违背了单例模式的初衷,也致使系统运行发生错误。java

      如何解决该问题?咱们至少有两种解决方案,在正式介绍这两种解决方案以前,先介绍一下单例类的两种不一样实现方式,饿汉式单例类和懒汉式单例类。编程

 

1.饿汉式单例类安全

      饿汉式单例类是实现起来最简单的单例类,饿汉式单例类结构图如图3-4所示:服务器

       从图3-4中能够看出,因为在定义静态变量的时候实例化单例类,所以在类加载的时候就已经建立了单例对象,代码以下所示:
[java]  view plain  copy
 
  1. class EagerSingleton {   
  2.     private static final EagerSingleton instance = new EagerSingleton();   
  3.     private EagerSingleton() { }   
  4.   
  5.     public static EagerSingleton getInstance() {  
  6.         return instance;   
  7.     }     
  8. }  
      当类被加载时,静态变量instance会被初始化,此时类的私有构造函数会被调用,单例类的惟一实例将被建立。若是使用饿汉式单例来实现负载均衡器LoadBalancer类的设计,则不会出现建立多个单例对象的状况,可确保单例对象的惟一性。
 

2.懒汉式单例类与线程锁定多线程

      除了饿汉式单例,还有一种经典的懒汉式单例,也就是前面的负载均衡器LoadBalancer类的实现方式。懒汉式单例类结构图如图3-5所示:并发

     从图3-5中能够看出,懒汉式单例在第一次调用getInstance()方法时实例化,在类加载时并不自行实例化,这种技术又称为 延迟加载(Lazy Load)技术,即须要的时候再加载实例,为了不多个线程同时调用getInstance()方法,咱们可使用 关键字synchronized,代码以下所示:
[java]  view plain  copy
 
  1. class LazySingleton {   
  2.     private static LazySingleton instance = null;   
  3.   
  4.     private LazySingleton() { }   
  5.   
  6.     synchronized public static LazySingleton getInstance() {   
  7.         if (instance == null) {  
  8.             instance = new LazySingleton();   
  9.         }  
  10.         return instance;   
  11.     }  
  12. }  
       该懒汉式单例类在getInstance()方法前面增长了关键字synchronized进行线程锁,以处理多个线程同时访问的问题。可是,上述代码虽然解决了线程安全问题,可是每次调用getInstance()时都须要进行线程锁定判断,在多线程高并发访问环境中,将会致使系统性能大大下降。如何既解决线程安全问题又不影响系统性能呢?咱们继续对懒汉式单例进行改进。事实上,咱们无须对整个getInstance()方法进行锁定,只需对其中的代码“instance = new LazySingleton();”进行锁定便可。所以getInstance()方法能够进行以下改进:
[java]  view plain  copy
 
  1. public static LazySingleton getInstance() {   
  2.     if (instance == null) {  
  3.         synchronized (LazySingleton.class) {  
  4.             instance = new LazySingleton();   
  5.         }  
  6.     }  
  7.     return instance;   
  8. }  
       问题貌似得以解决,事实并不是如此。若是使用以上代码来实现单例,仍是会存在单例对象不惟一。缘由以下:

      假如在某一瞬间线程A和线程B都在调用getInstance()方法,此时instance对象为null值,均能经过instance == null的判断。因为实现了synchronized加锁机制,线程A进入synchronized锁定的代码中执行实例建立代码,线程B处于排队等待状态,必须等待线程A执行完毕后才能够进入synchronized锁定代码。但当A执行完毕时,线程B并不知道实例已经建立,将继续建立新的实例,致使产生多个单例对象,违背单例模式的设计思想,所以须要进行进一步改进,在synchronized中再进行一次(instance == null)判断,这种方式称为双重检查锁定(Double-Check Locking)。使用双重检查锁定实现的懒汉式单例类完整代码以下所示:负载均衡

[java]  view plain  copy
 
  1. class LazySingleton {   
  2.     private volatile static LazySingleton instance = null;   
  3.   
  4.     private LazySingleton() { }   
  5.   
  6.     public static LazySingleton getInstance() {   
  7.         //第一重判断  
  8.         if (instance == null) {  
  9.             //锁定代码块  
  10.             synchronized (LazySingleton.class) {  
  11.                 //第二重判断  
  12.                 if (instance == null) {  
  13.                     instance = new LazySingleton(); //建立单例实例  
  14.                 }  
  15.             }  
  16.         }  
  17.         return instance;   
  18.     }  
  19. }  

       须要注意的是,若是使用双重检查锁定来实现懒汉式单例类,须要在静态成员变量instance以前增长修饰符volatile,被volatile修饰的成员变量能够确保多个线程都可以正确处理,且该代码只能在JDK 1.5及以上版本中才能正确执行。因为volatile关键字会屏蔽Java虚拟机所作的一些代码优化,可能会致使系统运行效率下降,所以即便使用双重检查锁定来实现单例模式也不是一种完美的实现方式。 函数

 

扩展高并发

IBM公司高级软件工程师Peter    Haggar 2004年在IBM developerWorks上发表了一篇名为《双重检查锁定及单例模式——全面理解这一失效的编程习语》的文章,对JDK    1.5以前的双重检查锁定及单例模式进行了全面分析和阐述,参考连接:http://www.ibm.com/developerworks/cn/java/j-dcl.html

 

3.饿汉式单例类与懒汉式单例类比较

      饿汉式单例类在类被加载时就将本身实例化,它的优势在于无须考虑多线程访问问题,能够确保实例的惟一性;从调用速度和反应时间角度来说,因为单例对象一开始就得以建立,所以要优于懒汉式单例。可是不管系统在运行时是否须要使用该单例对象,因为在类加载时该对象就须要建立,所以从资源利用效率角度来说,饿汉式单例不及懒汉式单例,并且在系统加载时因为须要建立饿汉式单例对象,加载时间可能会比较长。

      懒汉式单例类在第一次使用时建立,无须一直占用系统资源,实现了延迟加载,可是必须处理好多个线程同时访问的问题,特别是当单例类做为资源控制器,在实例化时必然涉及资源初始化,而资源初始化颇有可能耗费大量时间,这意味着出现多线程同时首次引用此类的机率变得较大,须要经过双重检查锁定等机制进行控制,这将致使系统性能受到必定影响。

【做者:刘伟  http://blog.csdn.net/lovelion

 

public class Singleton //: IDisposable
    {
        private Singleton()
        {

        }

        private static Singleton _Singleton = null;
        private static object Singleton_Lock = new object();

        public static Singleton CreateInstance()
        {
            if (_Singleton == null)//保证初始化以后,再也不等待锁
            {
                lock (Singleton_Lock)//保证单线程进入
                {
                    Console.WriteLine("进入锁");
                    if (_Singleton == null)//保证只初始化一次
                    {
                        _Singleton = new Singleton();
                    }
                }
            }
            return _Singleton;
        }
    }
相关文章
相关标签/搜索