第5章分布式系统模式 Singleton
上下文
在某些情况下,特定类型的数据需要提供给应用程序中的其他所有对象使用。在大多数情况下,这种类型的数据在系统中还是唯一的。例如,用户界面只能有一个所有应用程序必须访问的鼠标指针。同样,企业解决方案可能用单网关对象作为接口来管理与特定旧系统的连接。
影响因素
以下因素影响这种情况中的系统,在考虑上述问题的解决方案时必须协调这些影响因素:
- 很多编程语言(例如 Microsoft Visual Basic® 6.0 版或 C++)都支持全局对象的定义。这些对象位于命名空间的根部,应用程序中所有对象都可全局使用这些对象。此方法为全局可访问性问题提供了一个简单的解决方 案,但是不能解决单实例要求。它无法禁止其他对象创建全局对象的其他实例。另外,其他面向对象的语言(例如 Visual Basic .NET 或 C#)不直接支持全局变量。
- 要确保某类仅有一个实例,则必须控制实例化过程。因此需要通过使用编程语言中的固有实例化机制(例如通过使用 new 运算符)来阻止其他对象创建该类的实例。控制实例化的另一方面是提供集中机制,所有对象都可通过该机制获得对此单个实例的引用。
解决方案
Singleton 通过下列方法提供唯一的全局实例:
- 让类创建自己的唯一实例。
- 允许其他对象通过可返回实例引用的类方法来访问此实例。类方法是全局可访问的。
- 将类构造函数声明为私有,从而任何其他对象都不能创建新实例。
图 1 显示了该模式的静态结构。UML 类图表非常简单,这是因为 Singleton 由一个简单类组成,而该类包含了对其自身的单个实例的引用。
图 1:Singleton 结构
图 1 显示,Singleton 类包含了公用类作用范围(静态)属性,该属性会返回对 Singleton 类的单个实例的引用。(UML 中的下划线表示类作用范围属性。)另外,右上角的数字 1 表示任何时候在系统中该类只能有一个实例。因为 Singleton 的默认构造函数是私有的,因此系统中的任何其他对象都必须通过 Instance 属性才能访问 Singleton 对象。
通常将 Singleton 模式归类为惯用语,而不是模式,这是因为该解决方案主要取决于所用编程语言的功能(例如类方法和静态初始值)。正如该模式集合所做的那样,将抽象的概念与特定的实现分隔开来可能会使 Singleton 实现看起来非常简单。
结果上下文
Singleton 会导致下列优缺点:
优点
- 实例控制 。 Singleton 会阻止其他对象实例化其自己的 Singleton 对象的副本,从而确保所有对象都访问唯一实例。
- 灵活性。因为类控制了实例化过程,所以类可以灵活更改实例化过程。
缺点
- 开销。虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销。可以通过使用静态初始化解决此问题,相关叙述请参阅"在 C# 中实现 Singleton"。
- 可能的开发混淆。使用 singleton 对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用 new 关键字实例化对象。因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自己无法直接实例化此类。
- 对象生存期。 Singleton 不能解决删除单个对象的问题。在提供内存管理的语言中(例如基于 .NET Framework 的语言),只有 Singleton 类能够导致实例被取消分配,因为它包含对该实例的私有引用。在某些语言中(如 C++),其他类可以删除对象实例,但这样会导致 Singleton 类中出现悬浮引用。
在 C# 中实现 Singleton
上下文
要在 C# 中构建应用程序。您需要只有一个实例的类,并且需要提供一个用于访问实例的全局访问点。您希望确保您的解决方案高效,并且能够利用 Microsoft? .NET 公共语言运行库功能。您可能还希望确保解决方案是线程安全的。
实现策略
尽管 Singleton 是一种相对简单的模式,但是存在与具体实现有关的不同权衡因素和选项。下面是一组实现策略,及其优缺点的讨论。
Singleton
Singleton 设计模式的下列实现采用了 Design Patterns: Elements of Reusable Object-Oriented Software [Gamma95] 中所描述的解决方案,但对它进行了修改,以便利用 C# 中可用的语言功能,如属性:
using System;
public class Singleton
{
private static Singleton instance;
private Singleton() {}
public static Singleton Instance
{
get
{
if (instance == null)
{
instance = new Singleton();
}
return instance;
}
}
}
该实现主要有两个优点:
- 由于实例是在 Instance 属性方法内部创建的,因此类可以使用附加功能(例如,对子类进行实例化),即使它可能引入不想要的依赖性。
- 直到对象要求产生一个实例才执行实例化;这种方法称为"懒实例化"。懒实例化避免了在应用程序启动时实例化不必要的 singleton。
但是,这种实现的主要缺点是在多线程环境下它是不安全的。如果执行过程的不同线程同时进入 Instance 属性方法,那么可能会创建多个 Singleton 对象实例。每个线程都会执行下列语句,并决定必须创建新的实例:
if (instance == null)
解决此问题的方法有很多。一种方法是使用被称为 Double-Check Locking [Lea99] 的技术。而 C# 与公共语言运行库也提供了一种"静态初始化"方法,这种方法不需要开发人员显式地编写线程安全代码,即可解决这些问题。
静态初始化
One of the reasons Design Patterns [Gamma95] 避免使用静态初始化的原因之一是,C++ 规范在静态变量的初始化顺序方面留下了一些多义性。幸运的是,.NET Framework 通过其变量初始化处理方法解决了这种多义性:
public sealed class Singleton
{
private static readonly Singleton instance = new Singleton();
private Singleton(){}
public static Singleton Instance
{
get
{
return instance;
}
}
}
在此策略中,将在第一次引用类的任何成员时创建实例。公共语言运行库负责处理变量初始化。该类标记为 sealed 以阻止发生派生,而派生可能会增加实例。有关将类标记为 sealed 的利与弊的讨论,请参阅 [Sells03]。此外,变量标记为 readonly,这意味着只能在静态初始化期间(此处显示的示例)或在类构造函数中分配变量。
该实现与前面的示例类似,不同之处在于它依赖公共语言运行库来初始化变量。它仍然可以用来解决 Singleton 模式试图解决的两个基本问题:全局访问和实例化控制。公共静态属性为访问实例提供了一个全局访问点。此外,由于构造函数是私有的,因此不能在类本身以外实例化 Singleton 类;因此,变量引用的是可以在系统中存在的唯一的实例。
由于 Singleton 实例被私有静态成员变量引用,因此在类首次被对 Instance 属性的调用所引用之前,不会发生实例化。因此,与 Design Patterns 形式的 Singleton 一样,该解决方案实现了懒实例化属性的一种形式。
这种方法唯一的潜在缺点是,您对实例化机制的控制权较少。在 Design Patterns 形式中,您能够在实例化之前使用非默认的构造函数或执行其他任务。由于在此解决方案中由 .NET Framework 负责执行初始化,因此您没有这些选项。在大多数情况下,静态初始化是在 .NET 中实现 Singleton 的首选方法。
多线程 Singleton
静 态初始化适合于大多数情形。如果您的应用程序必须延迟实例化、在实例化之前使用非默认的构造函数或执行其他任务、并且工作在多线程环境中,那么您需要另一 种解决方案。但是,在一些情况下,您无法像在"静态初始化"示例中那样依赖公共语言运行库来确保线程的安全性。在这种情况下,必须使用特定的语言功能来确 保在存在多线程的情况下仅创建一个对象实例。更常见的解决方案之一是使用 Double-Check Locking [Lea99] 技术来阻止不同的线程同时创建 singleton 的新实例。
下面的实现仅允许一个线程在尚未创建 Singleton 实例的情况下进入关键区域(该区域由 lock 块标识)。
using System;
public sealed class Singleton
{
private static volatile Singleton instance;
private static object syncRoot = new Object();
private Singleton() {}
public static Singleton Instance
{
get
{
if (instance == null)
{
lock (syncRoot)
{
if (instance == null)
instance = new Singleton();
}
}
return instance;
}
}
}
此方法确保了仅在需要实例时才会创建仅一个实例。此外,变量被声明为 volatile,以确保只有在实例变量分配完成后才能访问实例变量。最后,此方法使用 syncRoot 实例来进行锁定(而不是锁定类型本身),以避免发生死锁。
此 double-check locking 方法解决了线程并发问题,同时避免在每个 Instance 属性方法的调用中都出现独占锁定。它还允许您将实例化延迟到第一次访问对象时发生。实际上,应用程序很少需要这种类型的实现。大多数情况下,静态初始化方法已经够用。
结果上下文
在 C# 中实现 Singleton 具有下列优缺点:
优点
- 由于 .NET Framework 显式地指定静态变量初始化如何以及何时发生,因此静态初始化方法是可能的。
- 列的前面的"多线程 Singleton"中所描述的 Double-Check Locking 技术已在公共语言运行库中正确实现。
缺点
如果您的多线程应用程序需要进行显式初始化,那么必须采取措施以避免线程问题。
第5章分布式系统模式 Singleton的更多相关文章
- 第5章分布式系统模式 使用服务器激活对象通过 .NET Remoting 实现 Broker
正在使用 Microsoft? .NET Framework 构建一个需要使用分布式对象的应用程序.您的要求包括能够按值或按引用来传递对象,无论这些对象驻留在同一台计算机上,还是驻留在同一个局域网 ( ...
- 第5章分布式系统模式 使用客户端激活对象通过 .NET Remoting 实现 Broker
正在 .NET 中构建一个需要使用分布式对象的应用程序,并且分布式对象的生存期由客户端控制.您的要求包括能够按值或按引用来传递对象,无论这些对象驻留在同一台计算 机上,还是驻留在同一个局域网 (LAN ...
- 第5章分布式系统模式 Broker(代理程序)
许多复杂的软件系统运行在多个处理器或分布式计算机上.将软件分布在多台计算机上的原因有多种,例如: 分布式系统可以利用多个 CPU 或一群低成本计算机的计算能力. 某个软件可能仅在特定计算机上可用. 出 ...
- 第5章分布式系统模式 在 .NET 中使用 DataSet 实现 Data Transfer Object
要在 .NET Framework 中实现分布式应用程序.客户端应用程序需要显示一个窗体,该窗体要求对 ASP.NET Web Service 进行多个调用以满足单个用户请求.基于性能方面的考虑,我们 ...
- 第5章分布式系统模式 Data Transfer Object(数据传输对象)
正在设计一个分布式应用程序,为了满足单个客户端请求,您发现自己对一个远程接口发出了多个调用,而这些调用所增加的响应时间超出了可接受的程度. 影响因素 在与远程对象通信时,请考虑下列需要权衡的因素: 远 ...
- 使用Micrisoft.net设计方案 第二章组织模式
第二章组织模式 模式不仅依赖于它所包含的更小模式,同时也依赖包含它的更大的模式.它是描述复杂软件的系统方法. 本章的目标是让我们了解以下问题: 1.如何标识模式与模式的关系 2.如何把模式组织成模式集 ...
- 单件模式Singleton来控制窗体被重复或多次打开
本文转载:http://blog.csdn.net/a0700746/article/details/4473796 一般在百度搜一下,会出来一下内容,看来很好用.Singleton很方便的一个用处就 ...
- 第 2 章 代理模式【Proxy Pattern】
第 2 章 代理模式[Proxy Pattern] 以下内容出自:24种设计模式介绍与6大设计原则.pdf 什么是代理模式呢?我很忙,忙的没空理你,那你要找我呢就先找我的代理人吧,那代理人总要知道被代 ...
- 第 1 章 策略模式【Strategy Pattern】
第 1 章 策略模式[Strategy Pattern] 以下内容出自: 24种设计模式介绍与6大设计原则.pdf 刘备要到江东娶老婆了,走之前诸葛亮给赵云(伴郎)三个锦囊妙计,说是按天机拆开解决棘手 ...
随机推荐
- java web 验证码 第一次不正确的问题,解决方案
首先是form表单 ,获取图片验证码 然后使用js 去服务器验证 问题: 第一次明明输入正确 ,确验证不了??那是因为你在form表单发起请求 和 ajax 发起的请求 地址 中 一个使用127. ...
- 又见古老的Typosquatting攻击:这次入侵Npm窃取开发者身份凭证
有些攻击方式虽然听起来很幼稚,但有时候却也可以生效,比如typosquatting攻击——我们上次看到这种攻击是在去年6月份,这本身也是种很古老的攻击方式. 所谓的typosquatting,主要是通 ...
- 017 SSH
Router>en Router#config t Enter configuration commands, one per line. End with CNTL/Z. Router(co ...
- PHP数组去空项
$strDelCodes = "A;B;;C;;C;D;;;D;D";$rsArray = array_values (array_unique (array_diff (spli ...
- 分析Linux内核的启动过程
第一章 环境 Ubuntu 14.10 Linux Kernel 3.18.6 第二章 代码及调试过程 环境搭建与内核准备: cd ~/LinuxKernel/ wget https://www.ke ...
- Java生成带logo 的二维码
这个工具类主要实现了两点功能: 1. 生成任意文链接的二维码. 2. 在二维码的中间加入图像. 主要实现步骤: 第一步: 导入QR二维码3.0 版本的core包和一张jpg图片(logo). core ...
- busybox的使用
1 将busybox设置为静态链接,放在文件系统中使用 make menuconfig的时候,Busybox Settings --> Build Options --> Build Bu ...
- jvm实例的个数
Generally speaking, each application will get its own JVM instance and its own OS-level process and ...
- ios31--NSThread
// // ViewController.m // 03-掌握-NSThread基本使用 #import "ViewController.h" #import "XMGT ...
- P3713 [BJOI2017]机动训练
这个题简直神仙,求相同路径的平方就等于两个人走相同路径的方案数.然后...暴力搜索+记忆化就行了,比较玄学. 题干: 题目描述 整个岛可以看作一片 n*m 的区域,每个格子有自己的地形. 一条路径由一 ...