线程安全单例最佳实践,C#中的Lazy是如何保证线程安全的
在.NET 4.0之后,.NET Framework中提供了一种安全的延迟加载类型Lazy
Lazy能够在多线程环境下,保证GetValue函数只执行一次,从而实现单例模式
在过去,实现单例模式我们通常使用二次判断锁,或者利用类的静态初始化函数
利用Lazy类型,能够简化这一过程,并且性能上更好
Lazy创建的时候可以指定线程安装模式,目前有两种模式,PublicationOnly,ExcutionAndPublication
PublicationOnly模式
boxed = CreateValue(); //
if (boxed == null ||
Interlocked.CompareExchange(ref m_boxed, boxed, null) != null) //
{
boxed = (Boxed)m_boxed; //
}
else
{
m_valueFactory = ALREADY_INVOKED_SENTINEL; //
}
1.运行初始化函数,装箱到一个内部Box类型中,解决null值判断的问题,如果已经创建的情况,会返回null,该过程是线程不安全的
2.判断m_boxed是否为空,m_boxed是value保存的字段,如果等于空则设置为boxed,该方法能保证原子性,该过程是线程安全的
3.如果CreateValue返回空,表示其他线程已经创建有实例,则设置为已经创建好的实例
4.将初始化方法标记为已经初始化,一般发生在并发运行情况下,多次运行CreateValue
PublicationOnly模式下使用基于Interlocked.CompareExchange实现的乐观锁,该类包含了原子性方法 CAS(Compare and swap)
CAS是利用CPU提供的原子性指令来实现,不同运行时版本可能有不一样实现
Interlocked具体的实现在Native方法中,有兴趣的朋友可以通过coreclr/jvm代码查看具体实现
这种模式下,单例函数可能多次运行,但是最终能保证获取到的实例只有一个
ExcutionAndPublication模式下使用的是Volatile+Monitor,Monitor就是lock语句的实现,Monitor实现在Native代码中,是重量级的锁
Monitor支持队列和线程睡眠,能够保证一整个方法块处于单线程执行状态
object threadSafeObj = Volatile.Read(ref m_threadSafeObj); //强制从主内存空间同步变量到线程内存空间副本
bool lockTaken = false;
try
{
if (threadSafeObj != (object)ALREADY_INVOKED_SENTINEL) //此时会有多个线程获取到正确值,抢夺开始
Monitor.Enter(threadSafeObj, ref lockTaken); //尝试等待锁,进入成功设置lockTaken为true
else
Contract.Assert(m_boxed != null);
//单线程代码块 Start
if (m_boxed == null) //没有设置值的情况
{
boxed = CreateValue(); //获取值
m_boxed = boxed; //设置到字段中
Volatile.Write(ref m_threadSafeObj, ALREADY_INVOKED_SENTINEL); //强制将线程内存空间副本写入到主内存空间
}
else // got the lock but the value is not null anymore, check if it is created by another thread or faulted and throw if so
{
boxed = m_boxed as Boxed;
if (boxed == null) // it is not Boxed, so it is a LazyInternalExceptionHolder
{
LazyInternalExceptionHolder exHolder = m_boxed as LazyInternalExceptionHolder;
Contract.Assert(exHolder != null);
exHolder.m_edi.Throw();
}
}
//单线程代码块End
}
finally
{
if (lockTaken) //进入成功需要释放,避免死锁
Monitor.Exit(threadSafeObj);
}
最新文章
- unrar.dll 使用实例
- ThoughtWorks.QRCode生成二维码
- 按列 sort 排序 Linux 如何查看当前占用CPU或内存最多的K个进程
- [Sqlite] --&;gt; Sqlite于Windows、Linux 和 Mac OS X 在安装过程
- Android Services (后台服务)
- Python类中的self到底是干啥的
- 21.C++- ";++";操作符重载、隐式转换之explicit关键字、类的类型转换函数
- Eureka配置instanceId显示IP
- Python爬虫之正则表达式(2)
- [math]本博客已经支持书写数学公式
- Navicat 连接MySQL时出现1251错误的解决方案
- 用JavaScript实现点击左侧列表右侧显示列表内容的方法
- Linux的基础命令
- 解决:angularjs radio默认选中失效问题
- POJ 2970 The lazy programmer
- WP8 调用webservice 错误 The remote server returned an error: NotFound 解决
- Thunder7.2.13.3884 JayXon
- linux修改系统时间时区
- C#中的多线程 - 并行编程 z
- 添加路由时啥时候是dev啥时候是gw
热门文章
- 2018.09.01 poj2689 Prime Distance(埃式筛法)
- docker入门实战
- SpringMVC上传图片总结(2)--- 使用百度webuploader上传组件进行上传图片
- Tensorflow currently has no official prebuild for your CUDA, cuDNN combination.
- MFC 怎样获得某个窗口的句柄?
- PAT甲 1046. Shortest Distance (20) 2016-09-09 23:17 22人阅读 评论(0) 收藏
- node csv
- Centos 7 安装 FFmpeg
- AI_ 视频监控-人体移动捕捉监测
- C#: 线程间操作无效: 从不是创建控件“dataGridView”的线程访问它