java中的单例模式

第二部分  单例模式

定义:保证一个类,只有一个实例存在,同时提供能对该实例加以访问的全局访问方法。
        单例模式是一种对象创建型模式,使用单例模式可以保证为一个类只生成唯一的实例对象。也就是说,在整个程序空间中,该类只存在一个实例
  对象。单例模式的要有三个:1)某个类只有一个实例;2)它必须自行创建这个实例;3)它必须自行向整个系统提供这个实例

2.单例模式深入分析
         单例模式适合于一个类只有一个实例的情况,如窗口管理器,打印缓冲池和文件系统,它们都是原型的例子。典型的情况是,那些对象的类型被遍及一个
         软件系统的不同对象访问,因此需要一个全局的访问指针,这便是众所周知的单例模式的应用。
         注意:使用单例模式时,构造函数一定是private类型的。
         在计算机系统中,需要管理的资源包括软件外部资源,例如,每台计算机可以有若干个打印机,但是有一个Printer Spooler,以避免两个打印作业同时
     传输到传真卡中的情况。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。需要管理的软件
     内部资源包括负责记录网站来访人数的部件,记录软件系统内部事件、出错信息的部件,或是对系统的表现进行检查的部件等,这些部件都必须集中管理。

1) 单例模式的第一个版本,采用的是"饿汉式",也就是当类加载进来时就立即实例化当前类的对象,这种方式比较消耗计算机的资源,代码如下:
package com.example.singleton;
public class SingletonOne {
         public static final SingletonOne singletonOne=new SingletonOne();

          public static SingletonOne getSingletonOne(){
                      return singletonOne;
          }
}
2)单例模式提供的第二个版本,"懒汉式",在单线程中能够非常好的工作,但是在多线程中存在线程安全问题
 package com.example.singleton;
  public class SingletonTwo {
      
            private static SingletonTwo singletonTwo;
             private SingletonTwo(){

               }

            public static SingletonTwo getSingletonTwo(){
                          if(null == singletonTwo){
                                        singletonTwo = new SingletonTwo();
            }
                     return singletonTwo;
          }
}

3)为解决多线程问题,采用对函数进行同步的方式,但是比较浪费资源,因为每次都要进行同步检查,而实际中真正需要好只是在第一次实例化时检查
即在实例化的时候使用synchronized 关键字来实现同步:
public static synchronized SingletonThree getSingletonThree(){
          if(null == singletonThree){
                  singletonThree = new SingletonThree();
          }
          return singletonThree;
}

4)既可以解决懒汉式,又可以解决多线程问题,还可以资源浪费的现象,看上去是一种不同的选择
public static SingletonFour getSingletonFour(){

            if(null == singletonFour){
                             synchronized(SingletonFour.class){
                                                if(null == singletonFour){
                                                              singletonFour = new SingletonFour();
                                                 }
                            }
             }
            return singletonFour;
}

另外也可以看一篇文章:

一.  单例模式简介

      单例(Singleton)模式是使用最广泛的设计模式。其思想意图是保证一个类只有一个实例,并且提供类对象的全程访问。单实例对象应用的范围很广:如GUI应用必须是单鼠标,MODEM的联接需要一条且只需要一条电话线,操作系统只能有一个窗口管理器,一台PC连一个键盘。使用全程对象能够保证方便地访问实例,但是不能保证只声明一个对象-也就是说除了一个全程实例外,仍然能创建相同类的本地实例。单实例模式通过类本身来管理其唯一实例,这种特性提供了问题的解决办法。唯一的实例是类的一个普通对象,但设计这个类时,让它只能创建一个实例并提供对此实例的全程访问。唯一实例类Singleton在静态成员函数中隐藏创建实例的操作。

二. 单例模式实现

     单例模式在java里有两个实现方式:1、懒汉模式; 2、饿汉模式。

     代码1、懒汉模式

  1. package cn.edu.hit.Singleton;  
  2.   
  3. /** 
  4.  * Singleton的懒汉模式 
  5.  * @author yuanliming 
  6.  * @created 2009-9-2 
  7.  */  
  8. public class Singleton   
  9. {  
  10.     private static Singleton singleton;  
  11.       
  12.     private Singleton()  
  13.     {     
  14.     }  
  15.       
  16.     public static Singleton getInstance()  
  17.     {  
  18.         if(null == singleton)  
  19.         {  
  20.             singleton = new Singleton();  
  21.         }  
  22.         return singleton;  
  23.     }  
  24. }  

     代码2、饿汉模式

  1. package cn.edu.hit.Singleton;  
  2.   
  3. /** 
  4.  * Singleton的饿汉模式 
  5.  * @author yuanliming 
  6.  * @created 2009-9-2 
  7.  */  
  8. public class AnotherSingleton  
  9. {  
  10.     private static AnotherSingleton singleton = new AnotherSingleton();   
  11.       
  12.     private AnotherSingleton()  
  13.     {  
  14.     }  
  15.       
  16.     public static AnotherSingleton getInstance()  
  17.     {  
  18.         return singleton;  
  19.     }  
  20. }  

    代码3. 测试代码 

  1. package cn.edu.hit.Singleton;  
  2.   
  3. /** 
  4.  * Test Singleton 
  5.  * @author yuanliming 
  6.  * @created 2009-9-2 
  7.  */  
  8. public class SingletonTest  
  9. {  
  10.     public static void main(String[] args)  
  11.     {  
  12.         Singleton s1 = Singleton.getInstance();  
  13.         Singleton s2 = Singleton.getInstance();  
  14.           
  15.         System.out.println(s1 == s2);   //return true  
  16.           
  17.         AnotherSingleton s3 = AnotherSingleton.getInstance();  
  18.         AnotherSingleton s4 = AnotherSingleton.getInstance();  
  19.           
  20.         System.out.println(s3 == s4);   //return true  
  21.     }  
  22. }  

     两种实现模式的比较:
     1、相同点:两种方式的构造函数都是私有的,对外的接口都是工厂方法。

    2、不同点:饿汉式是在类装载的时候直接得到该类的实例,可以说是前期绑定的;懒汉式是后期绑定的,类加载的时候uniSingleton是空的,在需要的时候才被创建且仅创建一次。饿汉式的速度快,效率高,但是耗费系统资源;懒汉式则相反。

    注意:懒汉式还存在一个问题,就是后期绑定不能确保对象只能被实例化一次。这就涉及到线程安全。

三. 单例模式的线程安全性探讨

       如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

       线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则就可能影响线程安全。

       单例模式下最关心的就是这个线程安全的问题了。上面我们提到懒汉模式会有线程安全的问题。当引入多线程时,就必须通过同步来保护getInstance() 方法。如果不保护 getInstance() 方法,则可能返回 Singleton 对象的两个不同的实例。假设两个线程并发调用 getInstance() 方法并且按以下顺序执行调用:

       1.   线程 1 调用 getInstance() 方法并决定 instance  //1 处为 null

      2.   线程 1 进入 if 代码块,但在执行 //2 处的代码行时被线程 2 预占。

      3.   线程 2 调用 getInstance() 方法并在 //1 处决定 instance  null

      4.   线程 2 进入 if 代码块并创建一个新的 Singleton 对象并在 //2 处将变量 instance 分配给这个新对象。

      5.   线程 2  //3 处返回 Singleton 对象引用。

      6.   线程 2 被线程 1 预占。

      7.   线程 1 在它停止的地方启动,并执行 //2 代码行,这导致创建另一个 Singleton 对象。

      8.   线程 1  //3 处返回这个对象。

    这样,getInstance()方法就创建了2Singleton对象,与单例模式的意图相违背。通过使用synchronized同步getInstance() 方法从而在同一时间只允许一个线程执行代码。代码如下:

    代码4

   

  1. package cn.edu.hit.Singleton;  
  2.   
  3. /** 
  4.  * Singleton的懒汉模式 
  5.  * @author yuanliming 
  6.  * @created 2009-9-2 
  7.  */  
  8. public class Singleton   
  9. {  
  10.     private static Singleton singleton;  
  11.       
  12.     private Singleton()  
  13.     {     
  14.     }  
  15.       
  16.     public static synchronized Singleton getInstance()  
  17.     {  
  18.         if(null == singleton)  
  19.         {  
  20.             singleton = new Singleton();  
  21.         }  
  22.         return singleton;  
  23.     }  
  24. }  

    此代码针对多线程访问 getInstance() 方法运行得很好。然而,分析这段代码,您会意识到只有在第一次调用方法时才需要同步。由于只有第一次调用执行了 //2 处的代码,而只有此行代码需要同步,因此就无需对后续调用使用同步。所有其他调用用于决定 instance 是非 null 的,并将其返回。多线程能够安全并发地执行除第一次调用外的所有调用。尽管如此,由于该方法是 synchronized 的,需要为该方法的每一次调用付出同步的代价,即使只有第一次调用需要同步。

因为代码4中只有//2需要同步,我们可以只将其包装到一个同步块中。得到的代码如下:

  1. package cn.edu.hit.Singleton;  
  2.   
  3. /** 
  4.  * Singleton的懒汉模式 
  5.  * @author yuanliming 
  6.  * @created 2009-9-2 
  7.  */  
  8. public class Singleton   
  9. {  
  10.     private static Singleton singleton;  
  11.       
  12.     private Singleton()  
  13.     {     
  14.     }  
  15.       
  16.     public static Singleton getInstance()  
  17.     {  
  18.         if(null == singleton)  
  19.         {  
  20.             synchronized (Singleton.class)  
  21.             {  
  22.                 singleton = new Singleton();  
  23.             }  
  24.         }  
  25.         return singleton;  
  26.     }  
  27. }  

    可是代码5出现了代码1同样的问题。当 instance  null 时,两个线程可以并发地进入 if 语句内部。然后,一个线程进入 synchronized 块来初始化 instance,而另一个线程则被阻断。当第一个线程退出 synchronized 块时,等待着的线程进入并创建另一个 Singleton 对象。注意:当第二个线程进入 synchronized 块时,它并没有检查 instance 是否非 null

为了解决代码5出现的问题,我们对instance进行两次检查,即“双重检查锁定”。代码如下:

    代码6

  1. package cn.edu.hit.Singleton;  
  2.   
  3. /** 
  4.  * Singleton的懒汉模式 
  5.  * @author yuanliming 
  6.  * @created 2009-9-2 
  7.  */  
  8. public class Singleton   
  9. {  
  10.     private static Singleton singleton;  
  11.       
  12.     private Singleton()  
  13.     {     
  14.     }  
  15.       
  16.     public static Singleton getInstance()  
  17.     {  
  18.         if(null == singleton)  
  19.         {  
  20.             synchronized (Singleton.class)  
  21.             {  
  22.                 if(null == singleton)  
  23.                 {  
  24.                     singleton = new Singleton();  
  25.                 }  
  26.             }  
  27.         }  
  28.         return singleton;  
  29.     }  
  30. }  

    双重检查锁定在理论上能够保证代码6只创建一个Singleton对象。假设有下列事件序列:

    1. 线程 1 进入 getInstance() 方法。

      2. 由于 instance  null,线程 1  //1 处进入 synchronized 块。

      3. 线程 1 被线程 2 预占。

      4. 线程 2 进入 getInstance() 方法。

      5. 由于 instance 仍旧为 null,线程 2 试图获取 //1 处的锁。然而,由于线程 1 持有该锁,线程 2  //1 处阻塞。

      6. 线程 2 被线程 1 预占。

      7. 线程 1 执行,由于在 //2 处实例仍旧为 null,线程 1 还创建一个 Singleton 对象并将其引用赋值给 instance

      8. 线程 1 退出 synchronized 块并从 getInstance() 方法返回实例。

      9. 线程 1 被线程 2 预占。

     10. 线程 2 获取 //1 处的锁并检查 instance 是否为 null

     11. 由于 instance 是非 null 的,并没有创建第二个 Singleton 对象,由线程 1 创建的对象被返回。

    看起来,双重检查锁定既解决了代码4的效率低下问题,又解决了代码5的线程安全性问题。但是它并不能保证它会在单处理器或多处理器计算机上顺利运行,根源在于 Java 平台内存模型。深入了解可以参考相关资料。

四.单例模式的选择

    无论以何种形式,都不应使用双重检查锁定,因为您不能保证它在任何 JVM 实现上都能顺利运行。

    如果只在单线程环境下运行,最好使用代码1

    如果涉及到多线程环境,最好使用代码2,也可以使用代码4(尽管效率低下,但可以保证线程同步)。





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章