写点什么

细颗粒度 Singleton 模式实现

  • 2007-09-27
  • 本文字数:4382 字

    阅读完需:约 14 分钟

背景讨论

作为一个很典型的设计模式,Singleton 模式常常被用来展示设计模式的技巧,并且随着技术的演进,.NET 语言和 Java 都已经把经典《Design Patterns : Elements of Reusable Object-Oriented Software》中所定义的 Singleton 模式作了完善,例如 C#可以通过这样一个非常精简但又很完美的方式实现了一个进程内部线程安全的 Singleton 模式。

C# 最经典 Singleton 模式的实现(Lazy 构造方式)- - - - - -

public class Singleton
{
private static Singleton instance; // 唯一实例
protected Singleton() { } // 封闭客户程序的直接实例化
public static Singleton Instance
{
get
{
if (instance == null)
instance = new Singleton();
return instance;
}
}
}

C# 通过 Double Check 实现的相对线程安全的 Singleton 模式- - - - - -

public class Singleton
{
protected Singleton() { }
private static volatile Singleton instance = null;
/// Lazy 方式创建唯一实例的过程
public static Singleton Instance()
{
if (instance == null) // 外层 if
lock (typeof(Singleton)) // 多线程中共享资源同步
if (instance == null) // 内层 if
instance = new Singleton();
return instance;
}
}

C#充分依靠语言特性实现的间接版 Singleton 模式 - - - - - - class Singleton
{
private Singleton() { }
public static readonly Singleton Instance = new Singleton();
}

但项目中我们往往需要更粗或者更细颗粒度的 Singleton,比如某个线程是长时间运行的后台任务,它本身存在很多模块和中间处理,但每个线程都希望有自己的线程内单独 Singleton 对象,其他线程也独立操作自己的线程内 Singleton,所谓的线程级 Singleton 其实他的实例总数 = 1(每个线程内部唯一的一个) * N (线程数)= N。

.NET 程序可以通过把静态成员标示为 System. ThreadStaticAttribute 就可以确保它指示静态字段的值对于每个线程都是唯一的。但这对于 Windows Form 程序很有效,对于 Web Form、ASP.NET Web Service 等 Web 类应用不适用,因为他们是在同一个 IIS 线程下分割的执行区域,客户端调用时传递的对象是在 HttpContext 中共享的,也就是说它本身不可以简单地通过 System. ThreadStaticAttribute 实现。不仅如此,使用 System. ThreadStaticAttribute 也不能很潇洒的套用前面的内容写成:

C#- - - - - -

[ThreadStatic]
public static readonly Singleton Instance = new Singleton();

因为按照.NET 的设计要求不要为标记为它的字段指定初始值,因为这样的初始化只会发生一次,因此在类构造函数执行时只会影响一个线程。在不指定初始值的情况下,如果它是值类型,可依赖初始化为其默认值的字段,如果它是引用类型,则可依赖初始化为 null。也就是说多线程情况下,除了第一个实例外,其他线程虽然也期望通过这个方式获得唯一实例,但其实获得就是一个 null,不能用。

解决 Windows Form 下的细颗粒度 Singleton 问题

对于 Windows Forms 下的情况,可以通过 System. ThreadStaticAttribute 比较容易的高速 CLR 其中的静态唯一属性 Instance 仅在本线程内部静态,但麻烦的是怎么构造它,正如上面背景介绍部分所说,不能把它放到整个类的静态构造函数里,也不能直接初始化,那么怎么办?还好,那个很 cool 的实现这里不适用的话,我们就退回到最经典的那个 lazy 方式加载 Singleton 实例的方法。你可能觉得,这线程不安全了吧?那种实现方式确实不是线程安全,但我们这里的 Singleton 构造本身就已经运行在一个线程里面了,用那种不安全的方式在线程内部实现只有自己“一亩三分地”范围内 Singleton 的对象反而安全了。新的实现如下:

C#- - - - - -

public class Singleton
{
private Singleton() { } [ThreadStatic] // 说明每个 Instance 仅在当前线程内静态

private static Singleton instance; public static Singleton Instance

{
get
{
if (instance == null)
instance = new Singleton();
return instance;
}
}
}

Unit Test - - - - - -

/// 每个线程需要执行的目标对象定义
/// 同时在它内部完成线程内部是否 Singleton 的情况
class Work
{
public static IList Log = new List();
/// 每个线程的执行部分定义
public void Procedure()
{
Singleton s1 = Singleton.Instance;
Singleton s2 = Singleton.Instance;
// 证明可以正常构造实例
Assert.IsNotNull(s1);
Assert.IsNotNull(s2);
// 验证当前线程执行体内部两次引用的是否为同一个实例
Assert.AreEqual(s1.GetHashCode(), s2.GetHashCode());
// 登记当前线程所使用的 Singleton 对象标识
Log.Add(s1.GetHashCode());
}
}[TestClass]

public class TestSingleton
{
private const int ThreadCount = 3;
[TestMethod]
public void Test()
{
// 创建一定数量的线程执行体
Thread[] threads = new Thread[ThreadCount];
for (int i = 0; i < ThreadCount; i++)
{
ThreadStart work = new ThreadStart((new Work()).Procedure);
threads[i] = new Thread(work);
}
// 执行线程
foreach (Thread thread in threads) thread.Start(); // 终止线程并作其他清理工作

// … … // 判断是否不同线程内部的 Singleton 实例是不同的

for (int i = 0; i < ThreadCount - 1; i++)
for (int j = i + 1; j < ThreadCount; j++)
Assert.AreNotEqual(Work.Log[i], Work.Log[j]);
}
}

下面我们分析一下单元测试代码说明的问题:

  • 在 Work.Procedure() 方法中,两次调用到了 Singleton 类的 Instance 静态属性,经过验证是同一个 Singleton 类实例。同时由于 Singleton 类的构造函数定义为私有,所以线程(客户程序)无法自己实例化 Singleton 类,因此同时满足该模式的设计意图;
  • 通过对每个线程内部使用的 Singleton 实例登记并检查,确认不同线程内部其实掌握的是不同实例的引用,因此满足我们需要实现的细颗粒度(线程级)的意图;
  • 解决 Web Form 下细颗粒度 Singleton 问题。

上面用 ThreadStatic 虽然解决了 Windows Form 的问题,但对于 Web Form 应用而言并不适用,原因是 Web Form 应用中每个会话的本地全局区域不是线程,而是自己的 HttpContext,因此相应的 Singleton 实例也应该保存在这个位置。实现上我们只需要做少许的修改,就可以完成一个 Web Form 下的细颗粒度 Singleton 设计:

注:这里的 Web Form 应用包括 ASP.NET Application、ASP.NET Web Service、ASP.NET AJAX 等相关应用。但示例并没有在.NET Compact Framework 和.NET Micro Framework 的环境下进行过验证。> C#- - - - - -

public class Singleton
{
/// 足够复杂的一个 key 值,用于和 HttpContext 中的其他内容相区别
private const string Key = “just.complicated…singleton”;
private Singleton() { } public static Singleton Instance

{
get
{
// 基于 HttpContext 的 Lazy 实例化过程
Singleton instance = (Singleton)HttpContext.Current.Items[Key];
if (instance == null)
{
instance = new Singleton();
HttpContext.Current.Items[Key] = instance;
}
return instance;
}
}
}

Unit Test - - - - - -

using System;
using System.Web;
using MarvellousWorks.PracticalPattern.SingletonPattern.WebContext;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace SingletonPattern.Test.Web
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
Singleton s1 = Singleton.Instance;
Singleton s2 = Singleton.Instance;
// 确认获得的 Singleton 实例引用确实已经被实例化了
Assert.IsNotNull(s1);
Assert.IsNotNull(s2);
// 确认两个引用调用的是同一个 Singleton 实例
Assert.AreEqual(s1.GetHashCode(), s2.GetHashCode());
// 显示出当前 Singleton 实例的标识,用于比较与其他
// HttpContext 环境下的 Singleton 实例其实是不同的实例
instanceHashCode.Text = s1.GetHashCode().ToString();
}
}
}

浏览器效果- - - - - -

同上,这段单元测试验证了 Web Form 下的细颗粒度 Singleton,通过将唯一实例的存储位置从当前线程迁移到 HttpContext,一样可以实现细颗粒度的 Singleton 设计意图。

更通用的细颗粒度 Singleton

但如果你是一个公共库或者是公共平台的设计者,您很难预料到自己的类库会运行在 Windows Form 还是 Web Form 环境下,但 Singleton 模式作为很多公共机制,最常用的包括技术器、时钟等等又常常会成为其他类库的基础,尤其当涉及到业务领域逻辑的时候,很难在开发过程就约定死运行的模式。怎么办?

这里借助一个工具类,通过它判断当前执行环境是 Web Form 还是 Windows Form,然后作一个 2 in 1 的细颗粒度 Singleton(,听起来有点象早年的任天堂游戏卡),不过就像我们提到的面向对象设计的单一职责原则一样,把两个和在一起会产生一些比较难看的冗余代码,但 Singleton 与其他设计模式有个很显著的区别——他不太希望被外部机制实例化,因为他要保持实例的唯一性,因此一些常用的依赖倒置技巧在这里又显得不太适用。这里实现一个稍有些冗余的 Web Form + Windows Form 2 in 1 的细颗粒度 Singleton 如下:

UML- - - - - -

C# 工具类 GenericContext- - - - - -

/// 判断当前应用是否为 Web 应用的 Helper 方法(非官方方法)
private static bool CheckWhetherIsWeb()
{
bool result = false;
AppDomain domain = AppDomain.CurrentDomain;
try
{
if (domain.ShadowCopyFiles)
result = (HttpContext.Current.GetType() != null);
}
catch (System.Exception){}
return result;
}

C# 2in 1 的细颗粒度 Singleton 模式实现- - - - - -

using System;
using System.Web;
using MarvellousWorks.PracticalPattern.Common;
namespace MarvellousWorks.PracticalPattern.SingletonPattern.Combined
{
public class Singleton
{
private const string Key = “marvellousWorks.practical.singleton”;
private Singleton() { } // 对外封闭构造
[ThreadStatic]
private static Singleton instance; public static Singleton Instance

{
get
{
// 通过之前准备的 GenericContext 中非官方的方法
// 判断当前执行模式是 Web Form 还是非 Web Form
// 本方法没有在 .NET 的 CF 和 MF 上验证过
if (GenericContext.CheckWhetherIsWeb()) // Web Form
{
// 基于 HttpContext 的 Lazy 实例化过程
Singleton instance = (Singleton)HttpContext.Current.Items[Key];
if (instance == null)
{
instance = new Singleton();
HttpContext.Current.Items[Key] = instance;
}
return instance;
}
else // 非 Web Form 方式
{
if (instance == null)
instance = new Singleton();
return instance;
}
}
}
}
}

小结

设计模式中很多意图部分表述的要求其实也都是有语意范围 的,比如说“唯一”、“所有相关”、“一系列相互依赖的”等,但项目中往往有自己定制化的要求,可能的话建议尽量用语言、语言运行环境的特性完成这些工作。

2007-09-27 21:511941
用户头像

发布了 61 篇内容, 共 10.7 次阅读, 收获喜欢 0 次。

关注

评论

发布
暂无评论
发现更多内容

开放签电子签章第一季度总结和第二季度目标

开放签开源电子签章

开源 电子合同 电子签章

聊聊Swift中的宏

珲少

华为云CodeArts IDE For Python 快速使用指南

华为云开发者联盟

云计算 华为云 华为云开发者联盟 华为云CodeArts 企业号2024年4月PK榜

智能调度|AIRIOT智能车队管理解决方案

AIRIOT

物联网平台 智慧系统 智能车队管理

系统logo及版权信息是否支持自定义配置 ?

开放签开源电子签章

版权保护 产品管理

科幻成真,未来已来!“2024上海智能机器人展会”大赏前瞻

AIOTE智博会

机器人展 智能机器人展 机器人展览会

多元 CPU 性能调优的技术挑战、产品设计和业务实践

百度Geek说

cpu 百度智能云 企业号 4 月 PK 榜 btune

芜湖等保测评机构有哪些?在哪里?

行云管家

等保 等保测评 芜湖

物联网浏览器(IoTBrowser)-整合机器学习yolo框架实现车牌识别

EquatorCoco

人工智能 机器学习 物联网

搞定了 6 种分布式ID,分库分表哪个适合做主键?

程序员小富

Java 分库分表 spring-boot

[图数据库]gStore1.2在Ubuntu和Java环境下的安装与试用

alexgaoyh

Java ubuntu 国产化 图数据库 gstore

智能制造领域智能问答系统

悦数图数据库

基于ChatGPT打造安全脚本工具流程

不在线第一只蜗牛

安全 ChatGPT

做AI产品经理的三年,聊聊我的心得

京东零售技术

人工智能 产品经理 企业号 4 月 PK 榜

长连接网关技术专题(十一):揭秘腾讯公网TGW网关系统的技术架构演进

JackJiang

即时通讯;IM;网络编程

国际标准图查询语言 GQL 正式发布,悦数图数据库业界首家原生支持

悦数图数据库

容器化Java程序秒级弹性伸缩实践

HelloGeek

Java 容器 弹性伸缩

电商新宠:淘宝拍立淘API接口助力精准搜索商品信息

技术冰糖葫芦

API 接口 API 测试 pinduoduo API

SSD 价格已经翻番,还要不要继续选择全闪?

XSKY星辰天合

EC 分布式全闪 三副本

深入理解Transformer技术原理 | 得物技术

得物技术

架构 Transformer AIGC 企业号 4 月 PK 榜 注意力机制

国密加密卡定义以及作用简单说明

行云管家

数据安全 国密 国密加密卡

从源码到实战:探寻App中的等待神秘力量,强制等待vs隐式等待

测试人

软件测试 自动化测试 测试开发

垃圾车的新用途:车外装摄像头,结合AI检查市容

算AI

人工智能 创业 AI 创新

天谋科技专家解析自动化领域正在经历的 IT 行业的老路

Apache IoTDB

生态短讯 | Tapdata 与 TDengine 完成产品兼容性互认证,打造物联网实时数据生态

tapdata

用户行为分析模型实践(四)—— 留存分析模型

vivo互联网技术

hive 数据分析 Clickhouse 留存分析模型

利用SEO服务器优化你的网站,获得更多曝光机会

一只扑棱蛾子

服务器

小小开发板承载万千创新可能,小熊派的云上奇遇记

华为云开发者联盟

物联网 华为云 华为云开发者联盟 先锋开发者云上说 企业号2024年4月PK榜

多高的学历才能轻松找到工作?这个热点有点扯吧~

王中阳Go

Go 面试题 大厂面经 求职面试 面试行情

LED显示屏品质受LED灯珠的影响

Dylan

性能 信息 LED显示屏 全彩LED显示屏 led显示屏厂家

细颗粒度Singleton模式实现_.NET_王翔_InfoQ精选文章