博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Singleton Best Practice
阅读量:5068 次
发布时间:2019-06-12

本文共 5974 字,大约阅读时间需要 19 分钟。

  Singleton,单例模式,是指只产生一个实例来访问。注意看注释,下面方法是AppDomain内的单例,当然,大多数程序都是在一个AppDomain中运行。

  比如下面代码就是简单的单例:

///
<summary>
///
Singleton instance per AppDomain, not thread safe
///
</summary>
///
<typeparam name="T"></typeparam>
public static
class
Singleton
<
T
>
where
T :
class
,
new
()
{
private
static
T _instance;
public
static
T Instance
{
get
{
if
(_instance
==
default
(T))
_instance
=
new
T();
return
_instance;
}
}
}

  但是它不是线程安全的,2个线程过来,很有可能创建2个实例,于是如果我们保证线程安全,还得加lock:

///
<summary>
///
Singleton instance per AppDomain
///
</summary>
///
<typeparam name="T"></typeparam>
public
static
class
Singleton
<
T
>
where
T :
class
,
new
()
{
private
static
T _instance;
private
static
readonly
object
_padLock
=
new
object
();
public
static
T Instance
{
get
{
lock
(_padLock)
{
//
only one thread can enter this scrope each time
if
(_instance
==
default
(T))
_instance
=
new
T();
return
_instance;
}
}
}
}

  虽然它已经线程安全,但是由于lock块,及时_instance != null,每次也要lock,多线程场景下性能差,所以可以采用Double Check:

///
<summary>
///
Singleton instance per AppDomain
///
</summary>
///
<typeparam name="T"></typeparam>
public
static
class
Singleton
<
T
>
where
T :
class
,
new
()
{
private
static
T _instance;
private
static
readonly
object
_padLock
=
new
object
();
public
static
T Instance
{
get
{
//
if _instance!= default(T), it does not lock, just return
if
(_instance
==
default
(T))
{
lock
(_padLock)
{
if
(_instance
==
default
(T))
_instance
=
new
T();
}
}
return
_instance;
}
}
}

  Double check在绝大多数情况下工作的很好,但是在多核心情况下会出现创建多个实例的情况。

  比如双核,CPU0执行到释放锁得时候,由于CPU高速缓存(cache)和写缓存(Store Buffer)的存在,主内存中可能_instance对象还没有创建,当CPU1执行到_instance==null时候,判断为true,然后接下去就创建了另外一个对象,因此,double check给出了2个完全不同的对象。

  根据  这篇文章来看,在多处理器环境下,可以这样解决问题:

///
<summary>
///
Singleton instance per AppDomain
///
</summary>
///
<typeparam name="T"></typeparam>
public
static
class
Singleton
<
T
>
where
T :
class
,
new
()
{
//
add volatile to make sure _instance exist in memory, not cpu cache
private
volatile
static
T _instance;
private
static
readonly
object
_padLock
=
new
object
();
public
static
T Instance
{
get
{
//
if _instance!= default(T), it does not lock, just return
if
(_instance
==
default
(T))
{
lock
(_padLock)
{
if
(_instance
==
default
(T))
_instance
=
new
T();
}
}
return
_instance;
}
}
}

  volitile关键字将让编译器认为是多线程代码,从而不会执行优化;同时保持内存中字段总是最新的,CPU缓存将不起作用。

  但是,加入volatile没有考虑到写缓存同步到主内存中,这样CPU0 new T()后,写入CPU0的cache中,而此时CPU1访问主存中的_instance,发现为null,于是它也new T(),这仅在IA64模式下可能发生,因为IA64目前是弱内存模型。而且加入volatile会带来性能开销(不用cpu cache),每次访问都访问内存,而且并发专家一再告诫我们:

     

  

  因此,我们可以使用Thread.MemoryBarrier()内存栅栏来保证CPU不进行乱序处理,解决Cache一致性。

  去掉了volatile关键字,一旦_instance初始化好,之后访问都可以利用Cpu cache。

///
<summary>
///
Singleton instance per AppDomain
///
</summary>
///
<typeparam name="T"></typeparam>
public
static
class
Singleton
<
T
>
where
T :
class
,
new
()
{
private
static
T _instance;
private
static
readonly
object
_padLock
=
new
object
();
public
static
T Instance
{
get
{
//
if _instance!= default(T), it does not lock, just return
if
(_instance
==
default
(T))
{
lock
(_padLock)
{
if
(_instance
==
default
(T))
{
var temp
=
new
T();
//
The processor executing the current thread cannot reorder instructions in such a way that
//
memory accesses prior to the call to MemoryBarrier execute after memory accesses that
//
follow the call to MemoryBarrier.
Thread.MemoryBarrier();
_instance
=
temp;
}
}
}
return
_instance;
}
}
}

  如果我们要进程内单例,可以使用Mutex来保证,同样如果是多进程内单例,可以使用命名Mutex:

///
<summary>
///
Singleton instance per AppDomain
///
</summary>
///
<typeparam name="T"></typeparam>
public
static
class
Singleton
<
T
>
where
T :
class
,
new
()
{
private
static
T _instance;
private
static
readonly
Mutex _mutex
=
new
Mutex();
public
static
T Instance
{
get
{
//
if _instance != default(T), it does not lock, just return
if
(_instance
==
default
(T))
{
_mutex.WaitOne();
if
(_instance
==
default
(T))
{
var temp
=
new
T();
//
The processor executing the current thread cannot reorder instructions in such a way that
//
memory accesses prior to the call to MemoryBarrier execute after memory accesses that
//
follow the call to MemoryBarrier.
Thread.MemoryBarrier();
_instance
=
temp;
}
_mutex.ReleaseMutex();
}
return
_instance;
}
}
}

  .net本身对静态初始化是单线程访问的,因此我们可以利用这个特性,写成如下代码:  

///
<summary>
///
Singleton instance per AppDomain
///
</summary>
///
<typeparam name="T"></typeparam>
public static
class
Singleton
<
T
>
where
T :
class, new
()
{
///
<summary>
///
Lazy load singleton instance
///
</summary>
public
static
T Instance
{
get
{
return
Nested.instance;
}
}
private
class
Nested
{
///
<summary>
///
.net make sure that during static constructor, it's thread safe per appdomain.
///
</summary>
internal
static
readonly
T instance
=
new
T();
}
}

当然,泛型约束为new()的话,意味着T必须有public constructor,如果没有呢?比如是private constructor怎么办?

其实一般来讲,人家用private constructor,就是为了不让你new 出来一个实例,如果你非得要搞出来,可以采用以下方法:

///
<summary>
///
Singleton instance per AppDomain
///
</summary>
///
<typeparam name="T"></typeparam>
public static
class
Singleton
<
T
> where T : class
{
///
<summary>
///
Lazy load singleton instance
///
</summary>
public
static
T Instance
{
get
{
return
Nested.instance;
}
}
private
class
Nested
{
///
<summary>
///
.net make sure that during static constructor, it's thread safe per appdomain.
///
</summary>
internal
static
readonly
T instance
=
(T)Activator.CreateInstance(
typeof
(T),
true
);
}
}

自从有了.net4.0的Lazy<T>后,还可以这样搞:

///
<summary>
///
Singleton instance per AppDomain
///
</summary>
///
<typeparam name="T"></typeparam>
public static
class
Singleton
<
T
>
where
T :
class, new
()
{
///
<summary>
///
static readonly make sure _lazy is thead safe and singleton.
///
using Lazy
<T>
make sure that it's lazy value creation is thead safe.
///
</summary>
private
static
readonly
Lazy
<
T
>
_lazy
=
new
Lazy
<
T
>
(()
=>
new
T(),
true
);
public
static
T Instance
{
get
{
return
_lazy.Value;
}
}
}

如此这般,就得到了线程安全的Singleton实例。当然我们还可以继续优化,比如有参数的构造函数,加入WeakReference等等…… 

转载于:https://www.cnblogs.com/prime/archive/2011/05/26/Singleton.html

你可能感兴趣的文章
软件测试必看的5本
查看>>
程序员必备的600单词
查看>>
hipster
查看>>
java:POI导出excel
查看>>
Web开发感悟:数据绑定是一种技术,更是一门艺术
查看>>
删除标题和边框
查看>>
JAVA第九次作业
查看>>
字符串反转,如将 '12345678' 变成 '87654321'
查看>>
Docker 安装 PHP+Nginx
查看>>
(转)MySQL排序原理与案例分析
查看>>
Miller-Rabin素数测试算法(POJ1811Prime Test)
查看>>
子路由配置
查看>>
grep和egrep正则表达式
查看>>
C语言中system函数用法解释
查看>>
Jsch初步
查看>>
[转] HBase异常:hbase-default.xml file seems to be for an old version of HBase
查看>>
C# Windows - SDI和MDI应用程序
查看>>
SDWebImage 加载显示 WebP 与性能问题
查看>>
java正则表达式(基本语法)
查看>>
入驻博客园
查看>>