0%

[c#]c#中的Lazy

线程安全单例最佳实践,C#中的Lazy是如何保证线程安全的#

在.NET 4.0之后,.NET Framework中提供了一种安全的延迟加载类型Lazy。Lazy能够在多线程环境下,保证GetValue函数只执行一次,从而实现单例模式。

在过去,实现单例模式我们通常使用二次判断锁,或者利用类的静态初始化函数利用Lazy类型,能够简化这一过程,并且性能上更好。

Lazy创建的时候可以指定线程安装模式,目前有两种模式,PublicationOnly,ExcutionAndPublication。

延迟初始化,在第一次使用该对象时再对其进行初始化,如果没有用到则不需要进行初始化,这样的话,使用延迟初始化就提高程序的效率,从而使程序占用更少的内存。

#### PublicationOnly模式
1
2
3
4
5
6
7
8
9
10
11
12
boxed = CreateValue(); //1
if (boxed == null ||
//比较m_boxed与null,如果不相等,什么都不做;如果m_boxed与null相等,则用boxed替换m_boxed的值。
//无论比较结果相等与否,返回值都是m_boxed中原有的值。
Interlocked.CompareExchange(ref m_boxed, boxed, null) != null) //2
{
boxed = (Boxed)m_boxed; //3
}
else
{
m_valueFactory = ALREADY_INVOKED_SENTINEL; //4
}
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模式#

ExcutionAndPublication模式下使用的是Volatile+Monitor,Monitor就是lock语句的实现,Monitor实现在Native代码中,是重量级的锁

Monitor支持队列和线程睡眠,能够保证一整个方法块处于单线程执行状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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);
}