标签:sea 不能 并发 循环 第一个 不同 public 取出 决定
一、介绍:
单例的实现,有四个共同特征:
请注意:所有这些实现还使用公共静态属性Instance 作为访问实例的方法。在所有情况下,可以轻松将属性转换为方法,而不会影响线程安全和性能。
二、单例的6个常见版本:
public sealed class Singleton1 { private static Singleton1 instance = null; private Singleton1() { } public static Singleton1 Instance { get { if (instance == null) { instance = new Singleton1(); } return instance; } } }
上面代码,不是线程安全的,两个不同线程都去竞争 if(instance == null) 时候,然后发现为true,会同时创建两个实例,这违反了单例原则。为了防止这种情况,我们想到了互斥锁,保证两个线程不会共同创建实例,请看第二个版本。
public sealed class Singleton2 { private static Singleton2 instance = null; private static readonly object padlock = new object(); Singleton2() { } public static Singleton2 Instance { get { lock (padlock)//加锁,保证两个线程并发时,不会重复创建对象,但问题是每次线程运行还得检查锁(性能略差) { if (instance == null) { instance = new Singleton2(); } return instance; } } } }
上述实现时线程安全的,加锁之后,防止多线程并发创建重复对象,但它有性能缺陷,因为每次使用Instance 调用实例时,都需要去判断锁,这样导致性能有影响,于是我们想到了第三个版本,双重判断加锁。
public sealed class Singleton3 { private static Singleton3 instance = null; private static readonly object padlock = new object(); Singleton3() { } public static Singleton3 Instance { get { if (instance == null) { lock (padlock) { if (instance == null) { instance = new Singleton3(); } } } return instance; } } }
该实现是线程安全的,不必每次都取出锁,即当第一次创建完实例后,下一次在调用实例,则不必再取出锁了。但该模式有四个缺点
public sealed class Singleton4 { private static readonly Singleton4 instance = new Singleton4(); static Singleton4() { }//显示静态构造函数告诉C# 编译器 private Singleton4() { } public static Singleton4 Instance { get { return instance; } } }
为什么说他不太懒惰?因为所谓懒惰是指我们要在调用实例时才去判断是否创建实例instance,然而这里,在类里先创建了实例。
为什么说他是线程安全的?因为 C# 中静态构造函数仅在 创建类的实例 或 引用类的静态成员时执行,举个例子,例如我在调用Instance 这个实例时,会执行第一句 instance = new Singleton4(),当我第二次再调用Instance 使用静态实例时,它则不会重复创建实例,所以它是线程安全的。
public sealed class Singleton5 { private Singleton5() { } public static Singleton5 Instance { get { return Nested.instance; } }//完全懒惰的,因为必须在执行这个Instance调用时,才会执行嵌套类的创建单例语句 private class Nested { static Nested() { } internal static readonly Singleton5 instance = new Singleton5();//此处要为internal的,外部要访问,不能是私有的。此处在外部调用时,需要创建单例对象时创建,实现完全懒惰 } }
上述代码是懒惰的,因为在封闭类中调用子类的instance时才去创建实例,而不是像第四个版本那样先创建了实例。请注意,尽管嵌套类可以访问封闭类中的私有成员,但是封闭类不能访问嵌套类内层,所以这里的嵌套类实例需要使用internal 关键字声明。
public sealed class Singleton6 { private static readonly Lazy<Singleton6> lazy = new Lazy<Singleton6>(() => new Singleton6()); public static Singleton6 Instance { get { return lazy.Value; } } private Singleton6() { } }
这里使用.NET4 或者更高版本,可以使用System.Lazy 这个类型声明懒惰的,线程安全的单例,同时他的性能非常好。
三、懒惰与性能
在许多情况下,其实不需要完全懒惰,除非您的初始化做了一些特别耗时的事情,或者其他地方产生了一些副作用,否则最好忽略上面所示的显示静态构造函数。这可以提高性能,因为它允许JIT编译器进行一次检查(例如在方法的开头)以确保类型已经初始化,然后从那时开始设定它。如果在相对紧密的循环中引用单例实例,则会产生(相对)显著的性能差异。您应该决定是否需要完全延迟实例化,并在类中适当地记录此决策。
参考原文:https://www.cnblogs.com/leolion/p/10241822.html
标签:sea 不能 并发 循环 第一个 不同 public 取出 决定
原文地址:https://www.cnblogs.com/vpersie2008/p/12272696.html