C# 单例模式实现方法 C#如何实现线程安全的单例模式
技术百科
畫卷琴夢
发布时间:2026-01-26
浏览: 次 直接 new Singleton() 不行,因静态字段初始化在多线程下不保证原子性,且无法延迟加载或控制时机;推荐用 Lazy 实现线程安全单例。
为什么直接 new Singleton() 不行
单例的核心是“全局唯一实例”,但裸写 private static Singleton instance = new Singleton(); 会触发类静态字段初始化,而 C# 的静态构造函数和字段初始化顺序在多线程下不保证原子性——尤其在 .NET Framework 早期版本中,可能创建多个实例。更关键的是,它无法控制初始化时机(比如依赖配置加载后才该创建),也不支持延迟加载。
推荐用 Lazy 实现线程安全单例
Lazy 是 .NET 4.0+ 内置的线程安全延迟初始化类型,默认使用 LazyThreadSafetyMode.ExecutionAndPublication,能确保只执行一次工厂逻辑、且所有线程看到的都是同一个实例。
- 无需手动加锁,无死锁风险
- 初始化失败会缓存异常,后续调用直接抛出,行为可预测
- 支持传入自定义工厂函数,便于注入依赖或做条件判断
public sealed class Singleton
{
private static readonly Lazy _instance = new Lazy(() => new Singleton());
public static Singleton Instance => _instance.Value;
private Singleton() { } // 私有构造,防止外部 new}
双重检查锁定(DCL)还能用吗
能用,但必须严格满足三个条件:字段用 volatile、两次判空、锁内再次判空。稍有遗漏就会在 x86/x64 指令重排下失效,导致部分线程拿到未完全构造的对象(表现为字段为默认值或 NullReferenceException)。
-
volatile 防止编译器/CPU 重排构造函数指令
- 第一次判空避免无谓加锁;第二次判空防止多个线程同时通过第一层检查后重复初始化
- .NET Core 2.1+ 对
volatile 语义更严格,但 DCL 仍比 Lazy 多出几条 IL 指令和锁开销
public sealed class Singleton
{
private static volatile Singleton _instance;
private static readonly object _lock = new object();
public static Singleton Instance
{
get
{
if (_instance == null)
{
lock (_lock)
{
if (_instance == null)
_instance = new Singleton();
}
}
return _instance;
}
}
private Singleton() { }}
静态构造函数方式的隐含限制
写成 static Singleton() { _instance = new Singleton(); } 看似简洁,但它会在**首次访问任意静态成员或创建实例时触发**,时机不可控;而且一旦抛出异常,该类型将永久不可用(TypeInitializationException),连 Instance 属性都无法再访问。

- 无法捕获初始化异常并降级处理
- 不支持参数化构造(比如读取 appsettings.json 后再创建)
- 在 ASP.NET Core 中,若单例依赖
IConfiguration,静态构造函数根本拿不到服务提供者
真正需要“绝对首次访问即创建”的场景极少,多数时候反而要避开它。
# 的是
# 都是
# 会在
# 加载
# 多个
# 首次
# app
# js
# json
# 对象
# c#
# 构造函数
# .net
# 为什么
# 线程
# Static
# 死锁
# private
# volatile
# 多线程
# 抛出
# 加锁
# 延迟加载
相关栏目:
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
AI推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
SEO优化<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
技术百科<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
谷歌推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
百度推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
网络营销<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
案例网站<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
精选文章<?muma echo $count; ?>
】
相关推荐
- 静态属性修改会影响所有实例吗_php作用域操作符下
- 如何使用Golang实现RPC序列化与反序列化_G
- Win10怎么设置开机密码_Windows10账户
- windows如何修改文件默认打开方式_windo
- php和redis连接超时怎么办_phpredis
- Win11怎么退出高对比度模式_Win11取消反色
- phpstudy本地环境mysql忘记密码_重置m
- PythonGIL机制理解_多线程限制解析【教程】
- PHP怎么接收前端传的时间戳_处理时间戳参数转换技
- Win11开机自检怎么关闭_跳过Win11开机磁盘
- Mac版Final Cut Pro入门_Mac视频
- 如何使用Golang sort排序切片_Golan
- Python字符串处理进阶_切片方法解析【指导】
- windows 10专注助手怎么关闭_window
- Windows10系统服务优化指南_Win10禁用
- Win11怎么解压RAR文件 Win11自带解压功
- c++如何打印函数堆栈信息_c++ backtra
- win11如何清理传递优化文件 Win11为C盘瘦
- Windows怎样关闭桌面弹窗广告_Windows
- Linux如何挂载新硬盘_Linux磁盘分区格式化
- Win10电脑怎么设置IP地址_Windows10
- 如何在Golang中实现文件下载_Golang文件
- Golang如何实现基本的用户注册_Golang用
- Win11怎么关闭触摸屏_禁用Win11笔记本触摸
- C++中的constexpr和const有什么区别
- 如何优化Golang程序CPU性能_Golang
- PythonFastAPI项目实战教程_API接口
- php订单日志权限怎么设_php订单日志文件权限设
- Win11开机Logo怎么换_Win11自定义启动
- c++如何使用std::bind绑定函数参数_c+
- MySQL 中使用 IF 和 CASE 实现查询字
- 如何在Golang中使用内置函数_Golangle
- Win11怎么恢复旧版开始菜单_通过软件还原Win
- Win11如何暂停系统更新 Win11暂停更新最长
- Python对象生命周期管理_创建销毁说明【指导】
- Win11应用商店下载慢怎么办 Win11更改DN
- 如何使用正则表达式精确匹配最多含一个换行符的 st
- Mac的“预览”如何合并多个PDF_Mac文件处理
- 如何在 Django 中修改用户密码后保持会话不丢
- PHP的FastAdmin架构适合二次开发吗_特点
- Windows11怎样开启游戏模式_Windows
- 如何使用Golang实现Web表单数据绑定_自动映
- Win11怎么设置ipv4地址_Windows 1
- php打包exe怎么传递参数_命令行参数接收方法【
- Win10怎样卸载iTunes_Win10卸载iT
- 如何在Golang中指定模块版本_使用go.mod
- c++的位运算怎么用 与、或、异或、移位操作详解【
- Win11怎么清理C盘下载文件夹_Win11清理下
- Win10如何更改开机密码_Windows10登录
- php485在macos下怎么配置_php485


QQ客服