《Head.First设计模式》的学习笔记(6)--单件模式

背景:
有一些对象其实我们只需要一个,比方说:线程池(threadpool)、缓存(cache)、对话框、处理偏好设置和注册表(registry)的对象、日志对象,充当打印机、显卡等设备的驱动程序的对象。事实上,这类对象只能有一个实例,如果制造出多个实例,就会导致许多问题产生,例如:程序的行为异常、资源使用过量,或者是不一致的结果。因此,我们设计这种类时必须确保只有一个实例,单件模式应运而生。

单件模式的意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

结构:

image

如何实现:

其实我们只需要把构造函数私有化,把new对象的过程进行控制就行了,具体代码如下:

 1public class Singleton
 2    {
 3        private static Singleton uniqueInstance; // 用一个静态变量来记录Singleton类的唯一实例
 4
 5        private Singleton()   // 构造器必须私有
 6        {
 7        }

 8
 9        public static Singleton Instance
10        {
11            get
12            {
13                if (null == uniqueInstance) // 如果uniqueInstance为空,说明对象没有被创建
14                {
15                    uniqueInstance = new Singleton();
16                }

17                return uniqueInstance;
18            }

19        }

20    }

21
但是上面的代码存在一个问题。当使用多线程时,产生的对象可能不只一个,分析见下图:
image 
如何解决这个问题:
a)、使用加锁机制,具体代码如下:
 1public class Singleton
 2    {
 3        private static Singleton uniqueInstance; // 用一个静态变量来记录Singleton类的唯一实例
 4        private static Object obj = new object();
 5        
 6        private Singleton()   // 构造器必须私有
 7        {
 8        }

 9
10        public static Singleton Instance
11        {
12            get
13            {
14                lock (obj)                      // 使用加锁,避免两个线程同时进入
15                {
16                    if (null == uniqueInstance) // 如果uniqueInstance为空,说明对象没有被创建
17                    {
18                        uniqueInstance = new Singleton();
19                    }

20                    return uniqueInstance;
21                }

22            }

23        }

24    }

25

b)、使用“急切”创建实例,不用延迟加载。具体代码如下:

 1public class Singleton
 2    {
 3        static Singleton uniqueInstance = new Singleton();  // 用一个静态变量来记录Singleton类的唯一实例
 4
 5        private Singleton()   // 构造器必须私有
 6        {
 7        }

 8
 9        public static Singleton Instance
10        {
11            get
12            {
13                return uniqueInstance;
14            }

15        }

16    }

17

c)、使用“双检锁”机制。具体代码如下:

 1public class Singleton
 2    {
 3        static Singleton uniqueInstance = null;  // 用一个静态变量来记录Singleton类的唯一实例
 4        private static Object obj = new object();
 5
 6        private Singleton()   // 构造器必须私有
 7        {
 8        }

 9
10        public static Singleton Instance
11        {
12            get
13            {
14                if (null == uniqueInstance)         // 先判断uniqueInstance是否为空,如果为空,再进行加锁 
15                {
16                    lock (obj)                      // 使用加锁,避免两个线程同时进入
17                    {
18                        if (null == uniqueInstance) // 如果uniqueInstance为空,说明对象没有被创建
19                        {
20                            uniqueInstance = new Singleton();
21                        }
                        
22                    }

23                }

24                return uniqueInstance;
25            }

26        }

27    }

28

对上面三种方法的评价:

1)、a方法采用了加锁机制,每次实例化都必须加锁,而加锁耗费的系统资源比较多,因此执行效率比较低,不推荐使用。

2)、b方法书写比较简单,但没有采用延迟加载,所以可能浪费部分资源。当使用频率高且运行时负担不重时推荐使用。

3)、c方法解决了多线程问题,并且只在第一次创建对象时加锁,执行效率比a方法高,推荐使用。

在什么情形下使用单例模式:

使用Singleton模式有一个必要条件:在一个系统要求一个类只有一个实例时才应当使用单例模式。反过来,如果一个类可以有几个实例共存,就不要使用单例模式。

注意:

a)、不要使用单例模式存取全局变量。这违背了单例模式的用意,最好放到对应类的静态成员中。

b)、不要将数据库连接做成单例,因为一个系统可能会与数据库有多个连接,并且在有连接池的情况下,应当尽可能及时释放连接。Singleton模式由于使用静态成员存储类实例,所以可能会造成资源无法及时释放,带来问题。

参考文献:

《Head.First设计模式》
  吕震宇  设计模式系列

 
Tag标签: 设计模式
posted @ 2008-04-15 16:30 鹰击长空 阅读(2015) 评论(19)  编辑 收藏 所属分类: 设计模式

  回复  引用  查看    
#1楼 2008-04-15 16:54 | 李战      
路过,学习


  回复  引用    
#2楼 2008-04-15 16:55 | cyberhedgehog [未注册用户]
基础的东西,帮顶了.
  回复  引用  查看    
#3楼 2008-04-15 16:58 | 姜敏      
请问楼主:
我在将gridview的数据导出为excel文件时,据说特别占用服务器资源,
此时可否用单例模式呢?

  回复  引用  查看    
#4楼 [楼主]2008-04-15 17:10 | 长空新雁      
@姜敏
如果为了节省资源,可以尝试单例模式。
  回复  引用  查看    
#5楼 2008-04-15 17:23 | 姜敏      
protected virtual void Export(GridViewEx grid, string extension, string contentType)
{
HttpResponse response = HttpContext.Current.Response;
//首先清除原有输出
response.Clear();
//下面的代码非常重要,缺少它您将得到一个时而正常,时而乱码的导出文件
StringBuilder metaHtml = new StringBuilder();
metaHtml.AppendFormat("<meta http-equiv='Content-Type';content='text/html';charset='{0}'>",
ResponseEncoding.WebName);
response.Write(metaHtml);
//因为名称中可能包含中文和非法字符,在添加到Header之前,需要将名称Url编码
string fileName = HttpUtility.UrlEncode(ExportFileName + extension, ResponseEncoding);
//通过content-disposition强制下载附件
response.AddHeader("content-disposition", String.Format("attachment;filename={0}", fileName));
response.Charset = ResponseEncoding.WebName;
response.ContentEncoding = ResponseEncoding;//设置输出流为简体中文
response.ContentType = contentType;
StringWriter stringWriter = new StringWriter();
HtmlTextWriter htmlWrite = new HtmlTextWriter(stringWriter);
grid.RenderControl(htmlWrite);
string content = ReplaceHref(stringWriter.ToString());
response.Write(content);
response.End();
}
这是具体的操作方法,是从jillzhang开源gridview中得来的,不知合不合适用单件模式
  回复  引用    
#6楼 2008-04-15 17:26 | RRz [未注册用户]
狗尾续貂
  回复  引用  查看    
#7楼 2008-04-15 17:37 | Tony Zhou      
支持
这本我看完了
  回复  引用  查看    
#8楼 2008-04-15 17:39 | 生鱼片      
支持
  回复  引用  查看    
#9楼 2008-04-15 17:41 | 姜敏      
请教LZ
当使用多线程时,产生的对象可能不只一个,分析见下图:
这样的实例是怎么来判断产生了多少个?
是用什么分析软件吗?
  回复  引用  查看    
#10楼 2008-04-15 17:57 | Midapex Village      
看起来是Visio的图,不过呢 软件没有分析功能,是人分析的,做试验也是会出现两个对象出来。楼主最好是把Singleton模式和模板结合起来用,更方便。比如Singleton,生成的时候不能简单那的new,应该从通过反射来生成,因为你的T的默认构造函数是私有的。
也最好讲讲Web程序中Singleton的对象的生存期。
  回复  引用  查看    
#11楼 2008-04-15 22:22 | Jim~      
正如版主所说~~C比A要好,节省更多资源,收藏啦
  回复  引用    
#12楼 2008-04-15 23:29 | 路人小刀 [未注册用户]
支持楼主继续坚持写下去,不过自己写的体会文章最好别照本宣科
  回复  引用  查看    
#13楼 2008-04-16 00:10 | 狼Robot      
学习.
  回复  引用  查看    
#14楼 [楼主]2008-04-16 08:39 | 长空新雁      
@李战,cyberhedgehog,姜敏,Tony Zhou,生鱼片,Midapex Village,Jim~,路人小刀 ,狼Robot
感谢大家的支持,我会继续坚持下去的
  回复  引用  查看    
#15楼 [楼主]2008-04-16 08:56 | 长空新雁      
@路人小刀
我去年才毕业,工作经验不是很多,所以自己的体会不是很深,但我会继续努力的,也希望你们这些高手指点。
@Midapex Village
你说的很对,简单的new不是很好,可以用配置文件和反射等来完成。但在这里只是为了简单的阐释单例模式,所以没有考虑那么多。
@姜敏
我的经验不是很丰富,但我想为了节省资源应该可以用单例。不过你这种情况一般不使用单例,除非有特殊目的。
  回复  引用    
#16楼 2008-04-16 08:57 | szbaby1221 [未注册用户]
写的很好,使我有学习的冲动!
不求深入,是我们初级代码工作者的通病,明知道这样,但是学习的动力又很小。
看了你的文章就要好好学习
  回复  引用  查看    
#17楼 2008-04-16 11:08 | 专研.NET      
巩固下
  回复  引用  查看    
#18楼 2008-05-17 13:42 | HedgeHog      
public static Singleton Instance
11 {
12 get
13 {
14 if (null == uniqueInstance) // 先判断uniqueInstance是否为空,如果为空,再进行加锁
15 {
16 lock (obj) // 使用加锁,避免两个线程同时进入
17 {
18 if (null == uniqueInstance) // 如果uniqueInstance为空,说明对象没有被创建
19 {
20 uniqueInstance = new Singleton();
21 }
22 }
23 }
24 return uniqueInstance;
25 }
26 }


这 最里层的判断是不是多余了?

lock (obj) // 使用加锁,避免两个线程同时进入 15 { 16 if (null == uniqueInstance) // 如果uniqueInstance为空,说明对象没有被创建 17 { 18 uniqueInstance = new Singleton(); 19 } 20 return uniqueInstance; 21 }

把 lock(obj)写到 if (null == uniqueInstance)里面,不就行了?
  回复  引用  查看    
#19楼 [楼主]2008-05-19 10:20 | 鹰击长空      
@ HedgeHog
最里层的判断不多余。主要是为了避免在多线程时在第一次判断和加锁之间被初始化。

标题  
姓名  
主页
Email (只有博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2008-04-15 17:16 编辑过
 
另存  打印