定义
单例模式就是保证无论是在多线程还是单线程中,始终保证一个类只有一个实例,并提供一个全局的访问接口。
最佳实践:
- 将构造函数设置为private
- 通过static修饰变量,保证全局仅有一个实例
使用场景:
- 全局计数器
- 唯一序列号
- new一个资源比较耗时的对象,比如数据库连接对象
代码
一.双锁实现单例【懒汉式】
Bash
public class Singleton
{
private static Singleton instance;
private readonly static object syncObj=new object();
private Singleton()
{}
public static Singleton GetInstance()
{
if(instance==null)
{
lock(syncObj)
{
if(instance==null)
{
instance=new Singleton();
}
}
}
return instance;
}
}
上面的代码为什么需要对instance做了两次判断呢?
假设多个线程同事调用这个方法,同时判断instance==null,然后通过第一层拦截,此时其中一个加锁了,那么另外一个只能等待,此时加锁的线程已经创建了一个实例,此时再做一次判断,第二个线程就没必要再new一个实例了,这样可以保证始终只存在一个这个类的实例。
一定要保证构造函数式私有的,这样可以防止new ,此外可以阻止其他类继承。
这种方式一定是安全的嘛?
答案是否定的。因为存在编译器模型的内存优化,存在先new 了一个空壳子,但是这个对象并没有初始化完成,此时虽然intance不是空,拿到这个实例是没法用的,存在错误的可能
解决方案:使用内存栅栏或者volatile变量可以解决内存优化的问题
Bash
public sealed class Singleton
{
private static Singleton instance = null;//volatile 可以禁止
private static readonly object padlock = new object();
Singleton()
{
}
public static Singleton Instance
{
get
{
if (instance == null)
{
lock (padlock)
{
if (instance == null)
{
var tmp= new Singleton();
// ensures that the instance is well initialized,
// and only then, it assigns the static variable.
System.Threading.Thread.MemoryBarrier();
instance = tmp;
}
}
}
return instance;
}
}
}
二.静态初始化实现单例【饿汉式】
这种方式和上面的双锁模式是类似的。
public class Singleton
{
private static readonly Singleton instance=new Singleton();
private Singleton()
{}
public Singleton GetInstance()
{
return instance;
}
}
三. 使用Lasy实现单例
public class Singleton
{
private static readonly Lazy<Singleton> lazy =
new Lazy<Singleton>(() => new Singleton());
public static Singleton Instance { get { return lazy.Value; } }
private Singleton()
{
}
}
这种方式线程安全的,但是可能性能比较差,他是通过LazyThreadSafetyMode.ExecutionAndPublication用作Lazy<Singleton>的线程安全模式。