【AICon】AI 基础设施、LLM运维、大模型训练与推理,一场会议,全方位涵盖! >>> 了解详情
写点什么

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

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

关注

评论

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

资源池以及资源池化是什么意思?

行云管家

资源池 IT运维 资源池化

如何用低成本方案解决室内超大场景下机器人定位与导航难题?

优必选科技

机器人 定位 导航

APP常用跨端技术栈深入分析

京东科技开发者

flutter H5 Weex ReactNative

单点登录的三种方式

Authing

云原生 SaaS SSO 单点登录 Authing

转转微服务框架的连接管理

转转技术团队

微服务 RPC 服务治理

TiDB 在长银五八消费金融核心系统适配经验分享

TiDB 社区干货传送门

安装 & 部署 OLAP 场景实践

[ Kitex 源码解读 ] 服务注册

baiyutang

Go 微服务架构 kitex CloudWeGo

Spring Cloud 与 K8s 的微服务设计

Damon

7月月更

万物根生,共创新时代:华为亮相第五届数字中国建设峰会

Geek_2d6073

参与开源社区还有证书拿?

胡说云原生

开源 证书

研发需求拆分的全流程详解 | 敏捷实践

LigaAI

开发者 研发管理 需求管理 需求分析 LigaAI

一文理解分布式开发中的服务治理

博文视点Broadview

2022可信云权威评估公布:天翼云获十项认证、五项最佳实践

Geek_2d6073

iOS中@class和#import

NewBoy

ios 前端 移动端 iOS 知识体系 7月月更

腾讯5G创新中心成立,布局无人港口、智慧矿山、电竞赛事等重点方向

科技热闻

Ticmp - 更快的让应用从 MySQL 迁移到 TiDB

TiDB 社区干货传送门

性能测评

TiCDC 架构和数据同步链路解析

TiDB 社区干货传送门

数据库架构设计 6.x 实践

TiDB之rawkv升级之路v5.0.4-->v6.1.0

TiDB 社区干货传送门

迁移 版本升级 集群管理

性能大规模专项评测双通过,数牍Tusita步入隐私大数据计算时代

Jessica@数牍

隐私计算性能 数牍科技 可信隐私计算评测

转转监控系统的内部原理及实践 审核中

转转技术团队

监控 Prometheus

专注B2B跨境支付的背后,XTransfer的风控基础设施是如何炼成的?

XTransfer技术

NFT是什么?如何开发NFT系统?

开源直播系统源码

数字藏品软件开发 数字藏品系统软件开发 数字藏品交易平台开发

数字电路基础篇

贾献华

7月月更

一文读懂Elephant Swap的LaaS方案的优势之处

西柚子

TiDB 在多点数字化零售场景下的应用

TiDB 社区干货传送门

实践案例 社区活动 TUG 话题探讨

深圳云堡垒机厂商哪家好?很贵吗?咨询电话多少?

行云管家

云计算 网络安全 堡垒机 云堡垒机

TiKV主要内存结构和OOM排查总结

TiDB 社区干货传送门

故障排查/诊断

MRS +Apache Zeppelin,让数据分析更便捷

华为云开发者联盟

大数据 开源 后端

万物皆可柯里化的 Ramda.js

掘金安东尼

前端 函数式编程 7月月更

黄东旭:TiDB的优势是什么?

TiDB 社区干货传送门

人物访谈

涅槃重生!字节大牛力荐大型分布式手册,凤凰架构让你浴火成神

冉然学Java

Java 华为 开源 网络协议 #Github

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