设计模式 - 单例

 

五种实现html

1.简单实现java

 

 

 1public sealed class Singleton
 2{
 3    static Singleton instance=null;
 4
 5    Singleton()
 6    {
 7    }

 8
 9    public static Singleton Instance
10    {
11        get
12        {
13            if (instance==null)
14            {
15                instance = new Singleton();
16            }

17            return instance;
18        }

19    }

20}
web

这种方式的实现对于线程来讲并非安全的,由于在多线程的环境下有可能获得Singleton类的多个实例。若是同时有两个线程去判断(instance == null),而且获得的结果为真,这时两个线程都会建立类Singleton的实例,这样就违背了Singleton模式的原则。实际上在上述代码中,有可能在计算出表达式的值以前,对象实例已经被建立,可是内存模型并不能保证对象实例在第二个线程建立以前被发现。编程

 

该实现方式主要有两个优势:设计模式

l         因为实例是在 Instance 属性方法内部建立的,所以类可使用附加功能(例如,对子类进行实例化),即便它可能引入不想要的依赖性。安全

l         直到对象要求产生一个实例才执行实例化;这种方法称为“惰性实例化”。惰性实例化避免了在应用程序启动时实例化没必要要的 singleton服务器

2.安全的线程 
多线程

 1public sealed class Singleton
 2{
 3    static Singleton instance=null;
 4    static readonly object padlock = new object();
 5
 6    Singleton()
 7    {
 8    }

 9
10    public static Singleton Instance
11    {
12        get
13        {
14            lock (padlock)
15            {
16                if (instance==null)
17                {
18                    instance = new Singleton();
19                }

20                return instance;
21            }

22        }

23    }

24}

25
26并发

 

这种方式的实现对于线程来讲是安全的。咱们首先建立了一个进程辅助对象,线程在进入时先对辅助对象加锁而后再检测对象是否被建立,这样能够确保只有一个实例被建立,由于在同一个时刻加了锁的那部分程序只有一个线程能够进入。这种状况下,对象实例由最早进入的那个线程建立,后来的线程在进入时(instence == null)为假,不会再去建立对象实例了。可是这种实现方式增长了额外的开销,损失了性能。负载均衡

3.双重锁定

 1public sealed class Singleton
 2{
 3    static Singleton instance=null;
 4    static readonly object padlock = new object();
 5
 6    Singleton()
 7    {
 8    }

 9
10    public static Singleton Instance
11    {
12        get
13        {
14            if (instance==null)
15            {
16                lock (padlock)
17                {
18                    if (instance==null)
19                    {
20                        instance = new Singleton();
21                    }

22                }

23            }

24            return instance;
25        }

26    }

27}

28

这种实现方式对多线程来讲是安全的,同时线程不是每次都加锁,只有判断对象实例没有被建立时它才加锁,有了咱们上面第一部分的里面的分析,咱们知道,加锁后还得再进行对象是否已被建立的判断。它解决了线程并发问题,同时避免在每一个 Instance 属性方法的调用中都出现独占锁定。它还容许您将实例化延迟到第一次访问对象时发生。实际上,应用程序不多须要这种类型的实现。大多数状况下咱们会用静态初始化。这种方式仍然有不少缺点:没法实现延迟初始化。

4.静态初始化

 1public sealed class Singleton
 2{
 3    static readonly Singleton instance=new Singleton();
 4
 5    static Singleton()
 6    {
 7    }

 8
 9    Singleton()
10    {
11    }

12
13    public static Singleton Instance
14    {
15        get
16        {
17            return instance;
18        }

19    }

20}

21

看到上面这段富有戏剧性的代码,咱们可能会产生怀疑,这仍是Singleton模式吗?在此实现中,将在第一次引用类的任何成员时建立实例。公共语言运行库负责处理变量初始化。该类标记为sealed 以阻止发生派生,而派生可能会增长实例。此外,变量标记为 readonly,这意味着只能在静态初始化期间(此处显示的示例)或在类构造函数中分配变量。

该实现与前面的示例相似,不一样之处在于它依赖公共语言运行库来初始化变量。它仍然能够用来解决 Singleton 模式试图解决的两个基本问题:全局访问和实例化控制。公共静态属性为访问实例提供了一个全局访问点。此外,因为构造函数是私有的,所以不能在类自己之外实例化 Singleton 类;所以,变量引用的是能够在系统中存在的惟一的实例。

因为 Singleton 实例被私有静态成员变量引用,所以在类首次被对 Instance 属性的调用所引用以前,不会发生实例化。

这种方法惟一的潜在缺点是,您对实例化机制的控制权较少。在 Design Patterns 形式中,您可以在实例化以前使用非默认的构造函数或执行其余任务。因为在此解决方案中由 .NET Framework 负责执行初始化,所以您没有这些选项。在大多数状况下,静态初始化是在 .NET 中实现 Singleton 的首选方法。

5.延迟初始化

 1public sealed class Singleton
 2{
 3    Singleton()
 4    {
 5    }

 6
 7    public static Singleton Instance
 8    {
 9        get
10        {
11            return Nested.instance;
12        }

13    }

14    
15    class Nested
16    {
17        static Nested()
18        {
19        }

20
21        internal static readonly Singleton instance = new Singleton();
22    }

23}

24

这里,初始化工做有Nested类的一个静态成员来完成,这样就实现了延迟初始化,并具备不少的优点,是值得推荐的一种实

现方式。

实现要点

l        Singleton模式是限制而不是改进类的建立。

l         Singleton类中的实例构造器能够设置为Protected以容许子类派生。

l         Singleton模式通常不要支持Icloneable接口,由于这可能致使多个对象实例,与Singleton模式的初衷违背。

l         Singleton模式通常不要支持序列化,这也有可能致使多个对象实例,这也与Singleton模式的初衷违背。

l         Singleton只考虑了对象建立的管理,没有考虑到销毁的管理,就支持垃圾回收的平台和对象的开销来说,咱们通常不必对其销毁进行特殊的管理。

l         理解和扩展Singleton模式的核心是“如何控制用户使用new对一个类的构造器的任意调用”。

 

 

l         能够很简单的修改一个Singleton,使它有少数几个实例,这样作是容许的并且是有意义的

优势

l         实例控制:Singleton 会阻止其余对象实例化其本身的 Singleton 对象的副本,从而确保全部对象都访问惟一实例

l         灵活性:由于类控制了实例化过程,因此类能够更加灵活修改实例化过程

缺点

l         开销:虽然数量不多,但若是每次对象请求引用时都要检查是否存在类的实例,将仍然须要一些开销。能够经过使用静态初始化解决此问题,上面的五种实现方式中已经说过了。

l          可能的开发混淆:使用 singleton 对象(尤为在类库中定义的对象)时,开发人员必须记住本身不能使用 new 关键字实例化对象。由于可能没法访问库源代码,所以应用程序开发人员可能会意外发现本身没法直接实例化此类。

l         对象的生存期:Singleton 不能解决删除单个对象的问题。在提供内存管理的语言中(例如基于 .NET Framework 的语言),只有 Singleton 类可以致使实例被取消分配,由于它包含对该实例的私有引用。在某些语言中(如 C++),其余类能够删除 
对象实例,但这样会致使 Singleton 类中出现悬浮引用。

适用性

l         当类只能有一个实例并且客户能够从一个众所周知的访问点访问它时。

l         当这个惟一实例应该是经过子类化可扩展的,而且客户应该无需更改代码就能使用一个扩展的实例时。

应用场景

l         每台计算机能够有若干个打印机,但只能有一个Printer Spooler,避免两个打印做业同时输出到打印机。 
(摘自吕震宇的
C#设计模式(7)-Singleton Pattern

l         PC机中可能有几个串口,但只能有一个COM1口的实例。

l         系统中只能有一个窗口管理器。

l         .NET Remoting中服务器激活对象中的Sigleton对象,确保全部的客户程序的请求都只有一个实例来处理。

完整示例

这是一个简单的计数器例子,四个线程同时进行计数。

 1using System; 
 2using System.Threading; 
 3 
 4namespace SigletonPattern.SigletonCounter 
 5
 6    /// <summary> 
 7    /// 功能:简单计数器的单件模式 
 8    /// 编写:Terrylee 
 9    /// 日期:2005年12月06日 
10    /// </summary>
 
11    public class CountSigleton 
12    
13        ///存储惟一的实例 
14        static CountSigleton uniCounter = new CountSigleton();   
15    
16        ///存储计数值 
17        private int totNum = 0;   
18    
19        private CountSigleton()  
20    
21        {  
22            ///线程延迟2000毫秒 
23            Thread.Sleep(2000); 
24        }
  
25    
26        static public CountSigleton Instance()  
27    
28        {  
29    
30            return uniCounter;  
31    
32        }
  
33         
34        ///计数加1 
35        public void Add() 
36        {  
37            totNum ++
38        }
   
39         
40        ///得到当前计数值 
41        public int GetCounter() 
42        {  
43            return totNum; 
44        }
  
45 
46    }
 
47}
 
48

 

 1using System;
 2using System.Threading;
 3using System.Text;
 4
 5namespace SigletonPattern.SigletonCounter
 6{
 7    /// <summary>
 8    /// 功能:建立一个多线程计数的类
 9    /// 编写:Terrylee
10    /// 日期:2005年12月06日
11    /// </summary>

12    public class CountMutilThread
13    {
14        public CountMutilThread()
15        {
16            
17        }

18
19        /// <summary>
20        /// 线程工做
21        /// </summary>

22        public static void DoSomeWork()
23        {
24            ///构造显示字符串
25            string results = "";
26
27            ///建立一个Sigleton实例
28            CountSigleton MyCounter = CountSigleton.Instance();
29
30            ///循环调用四次
31            for(int i=1;i<5;i++)
32            {
33                ///开始计数
34                MyCounter.Add();
35                
36                results +="线程";
37                results += Thread.CurrentThread.Name.ToString() + "——〉";
38                results += "当前的计数:";
39                results += MyCounter.GetCounter().ToString();
40                results += "\n";
41
42                Console.WriteLine(results);
43                
44                ///清空显示字符串
45                results = "";
46            }

47        }

48
49        public void StartMain()
50        {
51
52            Thread thread0 = Thread.CurrentThread; 
53   
54            thread0.Name = "Thread 0"
55   
56            Thread thread1 =new Thread(new ThreadStart(DoSomeWork)); 
57   
58            thread1.Name = "Thread 1"
59   
60            Thread thread2 =new Thread(new ThreadStart(DoSomeWork)); 
61   
62            thread2.Name = "Thread 2"
63   
64            Thread thread3 =new Thread(new ThreadStart(DoSomeWork)); 
65   
66            thread3.Name = "Thread 3"
67   
68            thread1.Start(); 
69   
70            thread2.Start(); 
71   
72            thread3.Start(); 
73            
74            ///线程0也只执行和其余线程相同的工做
75            DoSomeWork(); 
76        }

77    }

78}

79

 

 1using System; 
 2using System.Text; 
 3using System.Threading; 
 4 
 5namespace SigletonPattern.SigletonCounter 
 6
 7    /// <summary> 
 8    /// 功能:实现多线程计数器的客户端 
 9    /// 编写:Terrylee 
10    /// 日期:2005年12月06日 
11    /// </summary>
 
12    public class CountClient 
13    
14        public static void Main(string[] args) 
15        
16       CountMutilThread cmt = new CountMutilThread(); 
17 
18            cmt.StartMain(); 
19 
20            Console.ReadLine(); 
21        }
 
22    }
 
23}
 
24

 

 

 

 

前言:

这是一篇我见过的讲单例模式最完整的,也是讲的最好的一篇博客文章。


3. 1 单例模式的动机

      对于一个软件系统的某些类而言,咱们无须建立多个实例。举个你们都熟知的例子——Windows任务管理器,如图3-1所示,咱们能够作一个这样的尝试,在Windows的“任务栏”的右键弹出菜单上屡次点击“启动任务管理器”,看可否打开多个任务管理器窗口?若是你的桌面出现多个任务管理器,我请你吃饭,微笑(注:电脑中毒或私自修改Windows内核者除外)。一般状况下,不管咱们启动任务管理多少次,Windows系统始终只能弹出一个任务管理器窗口,也就是说在一个Windows系统中,任务管理器存在惟一性。为何要这样设计呢?咱们能够从如下两个方面来分析:其一,若是能弹出多个窗口,且这些窗口的内容彻底一致,所有是重复对象,这势必会浪费系统资源,任务管理器须要获取系统运行时的诸多信息,这些信息的获取须要消耗必定的系统资源,包括CPU资源及内存资源等,浪费是可耻的,并且根本没有必要显示多个内容彻底相同的窗口;其二,若是弹出的多个窗口内容不一致,问题就更加严重了,这意味着在某一瞬间系统资源使用状况和进程、服务等信息存在多个状态,例如任务管理器窗口A显示“CPU使用率”为10%,窗口B显示“CPU使用率”为15%,到底哪一个才是真实的呢?这纯属“调戏”用户,给用户带来误解,更不可取。因而可知,确保Windows任务管理器在系统中有且仅有一个很是重要。

         图3-1 Windows任务管理器

      回到实际开发中,咱们也常常遇到相似的状况,为了节约系统资源,有时须要确保系统中某个类只有惟一一个实例,当这个惟一实例建立成功以后,咱们没法再建立一个同类型的其余对象,全部的操做都只能基于这个惟一实例。为了确保对象的惟一性,咱们能够经过单例模式来实现,这就是单例模式的动机所在。

3. 2 单例模式概述

      下面咱们来模拟实现Windows任务管理器,假设任务管理器的类名为TaskManager,在TaskManager类中包含了大量的成员方法,例如构造函数TaskManager(),显示进程的方法displayProcesses(),显示服务的方法displayServices()等,该类的示意代码以下:

class TaskManager

{

     public TaskManager() {……} //初始化窗口

     public void displayProcesses()  {……} //显示进程

     public void  displayServices() {……} //显示服务

     ……

}

      为了实现Windows任务管理器的惟一性,咱们经过以下三步来对上类进行重构:

      (1)因为每次使用new关键字来实例化TaskManager类时都将产生一个新对象,为了确保TaskManager实例的惟一性,咱们须要禁止类的外部直接使用new来建立对象,所以须要将TaskManager的构造函数的可见性改成private,以下代码所示:

     private TaskManager() {……}

      (2)将构造函数改成private修饰后该如何建立对象呢?不要着急,虽然类的外部没法再使用new来建立对象,可是在TaskManager的内部仍是能够建立的,可见性只对类外有效。所以,咱们能够在TaskManager中建立并保存这个惟一实例。为了让外界能够访问这个惟一实例,须要在TaskManager中定义一个静态的TaskManager类型的私有成员变量,以下代码所示:

     private static TaskManager tm = null;

       (3)为了保证成员变量的封装性,咱们将TaskManager类型的tm对象的可见性设置为private,但外界该如何使用该成员变量并什么时候实例化该成员变量呢?答案是增长一个公有的静态方法,以下代码所示:

public static TaskManager getInstance()

{

    if (tm == null)

    {

        tm = new TaskManager();

    }

    return tm;

}

      在getInstance()方法中首先判断tm对象是否存在,若是不存在(即tm == null),则使用new关键字建立一个新的TaskManager类型的tm对象,再返回新建立的tm对象;不然直接返回已有的tm对象。

      须要注意的是getInstance()方法的修饰符,首先它应该是一个public方法,以便供外界其余对象使用,其次它使用了static关键字,即它是一个静态方法,在类外能够直接经过类名来访问,而无须建立TaskManager对象,事实上在类外也没法建立TaskManager对象,由于构造函数是私有的。 

思考

为何要将成员变量tm定义为静态变量?

       经过以上三个步骤,咱们完成了一个最简单的单例类的设计,其完整代码以下:

class TaskManager

{

     private static TaskManager tm = null;

     private TaskManager() {……} //初始化窗口

     public void  displayProcesses() {……} //显示进程

     public void  displayServices() {……} //显示服务

     public static TaskManager getInstance()

    {

        if (tm == null)

        {

            tm = new TaskManager();

        }

        return tm;

    }

    ……

}

      在类外咱们没法直接建立新的TaskManager对象,但能够经过代码TaskManager.getInstance()来访问实例对象,第一次调用getInstance()方法时将建立惟一实例,再次调用时将返回第一次建立的实例,从而确保实例对象的惟一性。

      上述代码也是单例模式的一种最典型实现方式,有了以上基础,理解单例模式的定义和结构就很是容易了。单例模式定义以下: 

单例模式(Singleton Pattern):确保某一个类只有一个实例,并且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。单例模式是一种对象建立型模式。

      单例模式有三个要点:一是某个类只能有一个实例;二是它必须自行建立这个实例;三是它必须自行向整个系统提供这个实例。

单例模式是结构最简单的设计模式一,在它的核心结构中只包含一个被称为单例类的特殊类。单例模式结构如图3-2所示:

      单例模式结构图中只包含一个单例角色:

● Singleton(单例):在单例类的内部实现只生成一个实例,同时它提供一个静态的getInstance()工厂方法,让客户能够访问它的惟一实例;为了防止在外部对其实例化,将其构造函数设计为私有;在单例类内部定义了一个Singleton类型的静态对象,做为外部共享的惟一实例。


3.3 负载均衡器的设计与实现

Sunny软件公司承接了一个服务器负载均衡(Load Balance)软件的开发工做,该软件运行在一台负载均衡服务器上,能够将并发访问和数据流量分发到服务器集群中的多台设备上进行并发处理,提升系统的总体处理能力,缩短响应时间。因为集群中的服务器须要动态删减,且客户端请求须要统一分发,所以须要确保负载均衡器的惟一性,只能有一个负载均衡器来负责服务器的管理和请求的分发,不然将会带来服务器状态的不一致以及请求分配冲突等问题。如何确保负载均衡器的惟一性是该软件成功的关键。

      Sunny公司开发人员经过分析和权衡,决定使用单例模式来设计该负载均衡器,结构图如图3-3所示:

        在图3-3中,将负载均衡器LoadBalancer设计为单例类,其中包含一个存储服务器信息的集合serverList,每次在serverList中随机选择一台服务器来响应客户端的请求,实现代码以下所示:

import java.util.*;

 

//负载均衡器LoadBalancer:单例类,真实环境下该类将很是复杂,包括大量初始化的工做和业务方法,考虑到代码的可读性和易理解性,只列出部分与模式相关的核心代码

class LoadBalancer

{

       //私有静态成员变量,存储惟一实例

       private  static LoadBalancer instance = null;

       //服务器集合

       private  List serverList = null;

      

       //私有构造函数

       private  LoadBalancer()

       {

              serverList  = new ArrayList();

       }

      

       //公有静态成员方法,返回惟一实例

       public  static LoadBalancer getLoadBalancer()

       {

              if  (instance == null)

              {

                     instance  = new LoadBalancer();

              }

              return  instance;

       }

      

       //增长服务器

       public  void addServer(String server)

       {

              serverList.add(server);

       }

      

       //删除服务器

       public  void removeServer(String server)

       {

              serverList.remove(server);

       }

      

       //使用Random类随机获取服务器

       public  String getServer()

       {

              Random  random = new Random();

              int  i = random.nextInt(serverList.size());

              return  (String)serverList.get(i);

       }

}

       咱们能够编写以下客户端代码对其进行测试:

class Client

{

       public  static void main(String args[])

       {

         //建立四个LoadBalancer对象

              LoadBalancer  balancer1,balancer2,balancer3,balancer4;

              balancer1  = LoadBalancer.getLoadBalancer();

              balancer2  = LoadBalancer.getLoadBalancer();

              balancer3  = LoadBalancer.getLoadBalancer();

              balancer4  = LoadBalancer.getLoadBalancer();

             

              //判断服务器负载均衡器是否相同

              if  (balancer1 == balancer2 && balancer2 == balancer3 &&  balancer3 == balancer4)

              {

                     System.out.println("服务器负载均衡器具备惟一性!");

              }

             

              //增长服务器

              balancer1.addServer("Server  1");

              balancer1.addServer("Server  2");

              balancer1.addServer("Server  3");

              balancer1.addServer("Server  4");

             

              //模拟客户端请求的分发

              for  (int i = 0; i < 10; i++)

           {

            String server =  balancer1.getServer();

                     System.out.println("分发请求至服务器: " + server);

       }

       }

}

       编译并运行程序,输出结果以下:

服务器负载均衡器具备惟一性!

分发请求至服务器:  Server 1

分发请求至服务器:  Server 3

分发请求至服务器:  Server 4

分发请求至服务器:  Server 2

分发请求至服务器:  Server 3

分发请求至服务器:  Server 2

分发请求至服务器:  Server 3

分发请求至服务器:  Server 4

分发请求至服务器:  Server 4

分发请求至服务器:  Server 1

       虽然建立了四个LoadBalancer对象,可是它们其实是同一个对象,所以,经过使用单例模式能够确保LoadBalancer对象的惟一性。


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

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

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

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

1.饿汉式单例类

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

       从图3-4中能够看出,因为在定义静态变量的时候实例化单例类,所以在类加载的时候就已经建立了单例对象,代码以下所示:

public class EagerSingleton

{

private static final  EagerSingleton instance = new EagerSingleton();

private EagerSingleton() { }

public static EagerSingleton getInstance()

{

return instance;

}

}

      当类被加载时,静态变量instance会被初始化,此时类的私有构造函数会被调用,单例类的惟一实例将被建立。若是使用饿汉式单例来实现负载均衡器LoadBalancer类的设计,则不会出现建立多个单例对象的状况,可确保单例对象的惟一性。

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

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

 

    从图3-5中能够看出,懒汉式单例在第一次调用getInstance()方法时实例化,在类加载时并不自行实例化,这种技术又称为延迟加载(Lazy Load)技术,即须要的时候再加载实例,为了不多个线程同时调用getInstance()方法,咱们可使用关键字synchronized,代码以下所示:

public class LazySingleton

{

private static LazySingleton instance = null;

 

private LazySingleton() { }

 

synchronized public  static LazySingleton getInstance()

{

if (instance == null)

{

instance = new  LazySingleton();

        }

return instance;

}

}

    该懒汉式单例类在getInstance()方法前面增长了关键字synchronized进行线程锁,以处理多个线程同时访问的问题。可是,上述代码虽然解决了线程安全问题,可是每次调用getInstance()时都须要进行线程锁定判断,在多线程高并发访问环境中,将会致使系统性能大大下降。如何既解决线程安全问题又不影响系统性能呢?咱们继续对懒汉式单例进行改进。事实上,咱们无须对整个getInstance()方法进行锁定,只需对其中的代码“instance = new LazySingleton();”进行锁定便可。所以getInstance()方法能够进行以下改进:

public static LazySingleton getInstance()

{

if (instance == null)

{

    synchronized  (LazySingleton.class)

{

instance = new LazySingleton();

            }

        }

return instance;

}

       问题貌似得以解决,事实并不是如此。若是使用以上代码来实现单例,仍是会存在单例对象不惟一。缘由以下:

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

public class LazySingleton

{

private volatile static  LazySingleton instance = null;

 

private LazySingleton() { }

 

public static LazySingleton getInstance()

{

if (instance == null)//第一重判断

{

    synchronized  (LazySingleton.class)//锁定代码块

{

if (instance == null)//第二重判断

{

    instance =  new LazySingleton();

}

            }

        }

return instance;

}

}

       须要注意的是,若是使用双重检查锁定来实现懒汉式单例类,须要在静态成员变量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.饿汉式单例类与懒汉式单例类比较

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

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


3.5 一种更好的单例实现方法

       饿汉式单例类不能实现延迟加载,无论未来用不用始终占据内存;懒汉式单例类线程安全控制烦琐,并且性能受影响。可见,不管是饿汉式单例仍是懒汉式单例都存在这样那样的问题,有没有一种方法,可以将两种单例的缺点都克服,而将二者的优势合二为一呢?答案是:Yes!下面咱们来学习这种更好的被称之为Initialization on Demand Holder (IoDH)的技术。

      在IoDH中,咱们在单例类中增长一个静态(static)内部类,在该内部类中建立单例对象,再将该单例对象经过getInstance()方法返回给外部使用,实现代码以下所示:

//Initialization on Demand Holder

public class Singleton

{

       private  Singleton()

{

       }

      

       private static class HolderClass

       {

              private final static Singleton  instance = new Singleton();

       }

      

       public static Singleton getInstance()

       {

              return HolderClass.instance;

       }

      

       public  static void main(String args[])

       {

              Singleton  s1, s2;

s1 = Singleton.getInstance();

              s2  = Singleton.getInstance();

              System.out.println(s1==s2);

       }

}

      编译并运行上述代码,运行结果为:true,即建立的单例对象s1s2为同一对象。因为静态单例对象没有做为Singleton的成员变量直接实例化,所以类加载时不会实例化Singleton,第一次调用getInstance()时将加载内部类HolderClass,在该内部类中定义了一个static类型的变量instance,此时会首先初始化这个成员变量,由Java虚拟机来保证其线程安全性,确保该成员变量只能初始化一次。因为getInstance()方法没有任何线程锁定,所以其性能不会形成任何影响。

      经过使用IoDH,咱们既能够实现延迟加载,又能够保证线程安全,不影响系统性能,不失为一种最好的Java语言单例模式实现方式(其缺点是与编程语言自己的特性相关,不少面向对象语言不支持IoDH)。

练习

分别使用饿汉式单例、带双重检查锁定机制的懒汉式单例以及IoDH技术实现负载均衡器LoadBalancer

      至此,三种单例类的实现方式咱们均已学习完毕,它们分别是饿汉式单例、懒汉式单例以及IoDH


3.6 单例模式总结

单例模式做为一种目标明确、结构简单、理解容易的设计模式,在软件开发中使用频率至关高,在不少应用软件和框架中都得以普遍应用。

1.主要优势

单例模式的主要优势以下:

(1) 单例模式提供了对惟一实例的受控访问。由于单例类封装了它的惟一实例,因此它能够严格控制客户怎样以及什么时候访问它。

(2) 因为在系统内存中只存在一个对象,所以能够节约系统资源,对于一些须要频繁建立和销毁的对象单例模式无疑能够提升系统的性能。

(3) 容许可变数目的实例。基于单例模式咱们能够进行扩展,使用与单例控制类似的方法来得到指定个数的对象实例,既节省系统资源,又解决了单例单例对象共享过多有损性能的问题。

2.主要缺点

单例模式的主要缺点以下:

(1) 因为单例模式中没有抽象层,所以单例类的扩展有很大的困难。

(2) 单例类的职责太重,在必定程度上违背了“单一职责原则”。由于单例类既充当了工厂角色,提供了工厂方法,同时又充当了产品角色,包含一些业务方法,将产品的建立和产品的自己的功能融合到一块儿。

(3) 如今不少面向对象语言(JavaC#)的运行环境都提供了自动垃圾回收的技术,所以,若是实例化的共享对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将从新实例化,这将致使共享的单例对象状态的丢失。

3.适用场景

在如下状况下能够考虑使用单例模式:

(1) 系统只须要一个实例对象,如系统要求提供一个惟一的序列号生成器或资源管理器,或者须要考虑资源消耗太大而只容许建立一个对象。

(2) 客户调用类的单个实例只容许使用一个公共访问点,除了该公共访问点,不能经过其余途径访问该实例。

原文做者:  http://blog.csdn.net/lovelion
相关文章
相关标签/搜索