开始第一种模式:单件模式(singleton)
概述:
单件模式(singleton)模式要求在全局中,类只有一个实例。整个网站中的任何一个客户(任意一个web请求)在访问这个类的时候,访问的都是类 的同一个实例。常见的现实中的比喻是美国总统,在全世界范围内,某一时刻,只有一个总统。即在网站或程序在从开始到结束运行的过程中,只有一个该类的实例。
如果做到让整个程序运行中只访问一个类的实例呢?首先当类实例不存在时,创建这个实例(建国时选举第一任总统)。然后,为了保证只使用一个类实例,那么必须不再新建类的实例(不再选举总统)。如何避免再次新建类的实例呢?可以有两种方式:
第一:让全国人民保证,自己再也不选举总统(在整个程序中,不再去实例化这个类),这个太难,人数众多(程序太大)。
第二:当需要总统的时候(比如:参加xxx国际会议?),不允许选举一个总统,只能到总统府去找。如果没有找到总统,则由议会推荐产生。
第一种,在程序中就是,大家约定好,要用到某个类的实例的时候不要去new ,把职责归属于使用(类)者。第二种,就是在程序中开发出一个类,这个类不能new ,只能够通过一种方法获得,比如静态属性,静态方法等。职责归属于,类的开发者。在单件模式中,我们要讨论的就是第二种情况:如何设计一个只能获得一个实例的类。
动机:
如何绕过常规的类构造器,提供一种机制,保证一个类只有一个实例。
意图:
保证一个类在整个程序,网站运行中只存在一个类的实例。并提供一个全局访问点。
VB.Net实现:
实现一:简单实现 --不推荐使用
- '访问计数器--单件模式--简单实现
- Public NotInheritable Class CountClient0
- Public IntCount As Int32 '计算值
- Private Shared _CountClient As CountClient0 = Nothing '初始对象
- Private Sub New()
- End Sub
- Public Shared ReadOnly Property CountClient() As CountClient0
- Get
- If _CountClient Is Nothing Then
- _CountClient = New CountClient0
- End If
- Return _CountClient
- End Get
- End Property
- End Class
- If _CountClient Is Nothing Then
- _CountClient = New CountClient0
- End If
这里的第一行,有可能有很多的程序同时访问这行(转换成汇编后,可能是很多行代码,因此并发访问这行可能性极大,等下将验证此问题),此时_CountClient均返回Nothing 。就是所谓的线性安全,在多线程环境下,会有问题。现在我们来检测是否存在线程安全问题,我们知道,线程安全主要是由于,多行同时访问上述代码第一行,都返回的是Nothing.这说明我们生成_CountClient对象的时间越长,这种可能行就越大。为了凸显这个问题,我们将这个简单实现的实例类改造如下:
- '访问计数器--单件模式--简单实现
- Public NotInheritable Class CountClient0
- Public IntCount As Int32 '计算值
- Private Shared _CountClient As CountClient0 = Nothing '初始对象
- Private Sub New()
- '延长生成对象时间
- For i = 0 To 10
- Threading.Thread.Sleep(2000)
- Dim b = i
- For j = 1 To 100000
- b = j + i
- If b > 1000000 Then b = -100000
- Next
- Next
- End Sub
- Public Shared ReadOnly Property CountClient() As CountClient0
- Get
- If _CountClient Is Nothing Then
- _CountClient = New CountClient0
- End If
- Return _CountClient
- End Get
- End Property
- End Class
测试代码:
Default.aspx
- <%@ Page Language="VB" AutoEventWireup="false" CodeFile="Default.aspx.vb" ValidateRequest ="false" Inherits="_Default" %>
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
- <html xmlns="http://www.w3.org/1999/xhtml">
- <head runat="server">
- <title>无标题页</title>
- </head>
- <body>
- <form id="form1" runat="server">
- <div>
- <input id="Button1" onclick ="javascript:out();" type="button" value="button" />
- <script>
- function out(){
- for(ii=0;ii<20;ii++){
- window.open('/default2.aspx?I='+ii);
- window.open('/Default.aspx?I='+ii);
- }
- }
- </script>
- </div>
- </form>
- </body>
- </html>
- Partial Class _Default
- Inherits System.Web.UI.Page
- Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
- Dim b = CountClient0.CountClient
- b.IntCount += 1
- Response.Write(b.IntCount & " I=" & Request("I") & "<Br>")
- End Sub
- End Class
为了实现多线程下的安全。进行如下设计
实现二:线程安全实现
- '访问计数器--单件模式--简单实现
- Public NotInheritable Class CountClient0
- Public IntCount As Int32 '计算值
- Private Shared _CountClient As CountClient0 = Nothing '初始对象
- Private Shared b As Object = New Object
- Private Sub New()
- For i = 0 To 10
- Threading.Thread.Sleep(2000)
- Dim b = i
- For j = 1 To 100000
- b = j + i
- If b > 1000000 Then b = -100000
- Next
- Next
- End Sub
- Public Shared ReadOnly Property CountClient() As CountClient0
- Get
- SyncLock (b)
- If _CountClient Is Nothing Then
- _CountClient = New CountClient0
- End If
- End SyncLock
- Return _CountClient
- End Get
- End Property
- End Class
因此为了解决上述缺陷,改进类设计如下:
实现三:双重加锁--不推荐使用
- '访问计数器--单件模式--简单实现
- Public NotInheritable Class CountClient0
- Public IntCount As Int32 '计算值
- Private Shared _CountClient As CountClient0 = Nothing '初始对象
- Private Shared b As Object = New Object
- Private Sub New()
- For i = 0 To 10
- Threading.Thread.Sleep(2000)
- Dim b = i
- For j = 1 To 100000
- b = j + i
- If b > 1000000 Then b = -100000
- Next
- Next
- End Sub
- Public Shared ReadOnly Property CountClient() As CountClient0
- Get
- If _CountClient Is Nothing Then
- SyncLock (b)
- If _CountClient Is Nothing Then
- _CountClient = New CountClient0
- End If
- End SyncLock
- End If
- Return _CountClient
- End Get
- End Property
- End Class
评述:这就实现了线程安全,同时不是每次都需要锁辅助对象。只有第一次创建对象时加锁。但在.Net平台有更为高效和简洁的实现方式,如下:
实现四:静态初始化
- '访问统计器--单件模式--
- Public NotInheritable Class CountClient
- Public IntCount As Int32 '计算值
- Shared ReadOnly _CountClient = New CountClient()
- Private Sub New()
- End Sub
- Public Shared ReadOnly Property Instance() As CountClient
- Get
- Return _CountClient
- End Get
- End Property
- End Class
实现五:延迟初始化
- '访问计数器--单件模式--延迟初始化
- Public NotInheritable Class CountClient1
- Public IntCount As Int32 '计算值
- Private Sub New()
- End Sub
- Public ReadOnly Property Intance()
- Get
- '在这里进行延迟处理
- Return Nested._CountClient
- End Get
- End Property
- Class Nested
- Private Sub New()
- End Sub
- Friend Shared _CountClient As CountClient1 = New CountClient1
- End Class
- End Class
实现五:泛型单件
- '访问计数器--单件模式--泛型单件
- Public NotInheritable Class CountClient3(Of T)
- Public IntCount As Int32 '计算值
- Private Sub New()
- End Sub
- Public ReadOnly Property Intance() As CountClient3(Of T)
- Get
- '在这里进行延迟处理
- Return Nested._CountClient
- End Get
- End Property
- Class Nested
- Private Sub New()
- End Sub
- Friend Shared _CountClient = New CountClient3(Of T)
- End Class
- End Class
注意事项:
1:使用Singleton模式有一个必要条件:在一个系统要求一个类只有一个实例时才应当使用单例模式。反过来,如果一个类可以有几个实例共存,就不要使用单例模式。
2:不要使用单例模式存取全局变量。这违背了单例模式的用意,最好放到对应类的静态成员中。
3:不要对单件类进行系列化。他违背了单件模式的初衷。
应用场景:
1:每台计算机可以有若干个打印机,但只能有一个Printer Spooler,避免两个打印作业同时输出到打印机。
2:一个具有自动编号主键的表可以有多个用户同时使用,但数据库中只能有一个地方分配下一个主键编号。否则会出现主键重复。
3:负载均衡对象。在负载均衡模型中,有多台服务器可提供服务,任务分配器随机挑选一台服务器提供服务,以确保任务均衡(实际情况比这个复杂的多)。这里,任务分配实例只能有一个,负责挑选服务器并分配任务。
4:Mdi子窗体,只能打开一个的情况下。
常见问题:
1:把数据库的链接设计成单件模式好不好,为什么?
--不要将数据库连接做成单例,因为一个系统可能会与数据库有多个连接,并且在有连接池的情况下,应当尽可能及时释放连接。在并发时 会损失性能 很大。Singleton模式由于使用静态成员存储类实例,所以可能会造成资源无法及时释放,带来问题。
2:静态类和单件的区别?
--单件具备面向对象的一些特点,比如继承等,静态类没有。
--静态类,Java中有说法为工具类,谈不上面向对象的概念
--单件似乎存在难回收问题(GC不自动回收)
应用实例:
1:日志记录
参考资料: