HarmonyOS开发者限时福利来啦!最高10w+现金激励等你拿~ 了解详情
写点什么

细颗粒度 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:511949
用户头像

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

关注

评论

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

前端学习

阡陌r

爆款歌曲的养成背后,是哪些因素在推动?

HIFIVE音加加

娱乐 音乐 娱乐社交 热门 华语音乐

架构实战营模块八作业

程序员小张

「架构实战营」

前端学习路径

阡陌r

阿里云消息队列 Kafka 生态集成的实践与探索

阿里巴巴中间件

kafka 阿里云 云原生 消息队列

巧用GenericObjectPool创建自定义对象池

京东科技开发者

京东云 API 编排 对象池 京东物流 企业号 3 月 PK 榜

统一观测丨使用 Prometheus 监控 E-MapReduce,我们该关注哪些指标?

阿里巴巴中间件

阿里云 云原生

全面图像编辑:ON1 Photo RAW 2023激活版

真大的脸盆

Mac 图像处理 Mac 软件 图像编辑 raw

火山引擎DataTester:一个A/B测试,将一款游戏的核心收益提升了8%

字节跳动数据平台

大数据 游戏 AB testing实战 企业号 3 月 PK 榜

初识大热的ChatGPT的几点思考|社区征文

穿过生命散发芬芳

ChatGPT

Portraiture2023人像免费磨皮插件

茶色酒

Portraiture2023

这款 IDEA 插件太好用了,堪称日志管理神器!

程序知音

Databend query result cache 设计与实现

Databend

GreatSQL 8.0.25-17今日发布

GreatSQL

greatsql greatsql社区

极氪汽车 APP 系统云原生架构转型实践

阿里巴巴中间件

阿里云 云原生

工作一年,我重新理解了《重构》

阿里巴巴中间件

阿里云 云原生 重构

经验分享:高德地图如何短时间快速完成春节出行备战工作?

阿里巴巴中间件

阿里云 云原生 函数计算

如何有效的进行用例评审

京东科技开发者

用例 京东云 代码评审 企业号 3 月 PK 榜

社交软件的月活利器:从UGC到互娱

HIFIVE音加加

互联网 软件 社交 科技 社交媒体

Matlab常用图像处理命令108例(三)

timerring

图像处理

订单超时怎么处理?我们用这种方案

阿里巴巴中间件

阿里云 云原生

浅谈kafka

京东科技开发者

数据库 中间件 京东云 kafka manager 企业号 3 月 PK 榜

async 与 Thread 的错误结合

newbe36524

C#

如何科学管理技术团队的研发交付速率?

LigaAI

DevOps 研发效能 技术管理 效能度量 企业号 3 月 PK 榜

基于 Flink 流计算实现的股票交易实时资产应用

Apache Flink

大数据 flink 实时计算

FL Studio21中文语言版水果编曲工具

茶色酒

FL Studio21

Orika JavaBean映射工具使用

京东科技开发者

JAVA开发 京东云 JavaBean 企业号 3 月 PK 榜

详解AQS的7个同步组件

华为云开发者联盟

后端 开发 华为云 华为云开发者联盟 企业号 3 月 PK 榜

5个高并发导致数仓资源类报错分析

华为云开发者联盟

数据库 后端 华为云 华为云开发者联盟 企业号 3 月 PK 榜

架构师日记-软件高可用实践那些事儿

京东科技开发者

高可用 软件架构 京东云 企业号 3 月 PK 榜

从零打造你的前端开发脚手架

南城FE

前端 js 前端工程化 脚手架

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