带锁的单例属性如何保证线程安全?

How does a singleton property with a lock ensure thread safety?

我很少使用单例,在这种情况下是合适的。在尝试调查其最佳实现时,我遇到了这段代码,这让我相信我不正确地理解括号如何封装 "scope."

public sealed class Singleton
{
    private static Singleton instance = null;
    private static readonly object padlock = new object();

    Singleton()
    {
    }

    public static Singleton Instance
    {
        get
        {
            lock (padlock)
            {
                if (instance == null)
                {
                    instance = new Singleton();
                }
                return instance;
            }
        }
    }
} 

我很困惑当我尝试访问时会发生什么 "Instance." 假设我正在开发一个日志记录单例(我对单例有用的应用程序)并且它有一个方法 "WriteLine(string line)"

当我打电话时:

Singleton.Instance.WriteLine("Hello!");

它在"WriteLine?"

的整个方法执行期间保持锁

如果我将实例分配给外部变量会怎样:

Singleton Console = Singleton.Instance;

现在在单例之外不断引用单例。 Console.WriteLine("Hello!") 是否也像 Singleton.Instance.WriteLine("Hello!") 一样完全线程安全?

无论如何,我只是很困惑这如何使单例线程安全以及它是否仅在 属性 被显式访问时才是线程安全的。我以为Singlton.Instance.WriteLine("...")会先拉出Instance,从而离开锁的范围,然后在返回的实例上执行WriteLine,因此在释放锁后执行写入。

任何帮助消除我对这个功能如何理解的误解的帮助。

不,锁定结束于 return,您对 Instance 所做的任何操作都是 "outside" 锁定。

lock在这一点上的优势只有一个:

  • 保证只能创建一个Singleton实例。

注意,一般情况下,最好使用Lazy<>class。要获得相同的结果,您必须像这样使用它:

public static Lazy<Singleton> Instance = new Lazy<Singleton>();

Lazy<T>可以工作三个modes,默认一个,ExecutionAndPublication,相当于那个代码)

Does Singleton.Instance.WriteLine("Hello!"); maintain the lock during the execution of the entire method of WriteLine?

不,锁只保护你的单例的创建。 WriteLine 执行解锁(当然,除非它在内部获得自己的锁)。

Is Console.WriteLine("Hello!") also completely thread safe like Singleton.Instance.WriteLine("Hello!")?

它与 Singleton.Instance 一样安全或不安全,因为锁不会在 Instance 的 getter 之外维护。

Anyway, I'm just confused how this makes the singleton thread safe

Lock 使获取单例实例的过程成为线程安全的。让你的单例线程安全的方法是一个不依赖于你的对象是否是单例的过程。没有简单的一站式万能解决方案可以使线程不安全的对象以线程安全的方式运行。您一次用一种方法解决它。

Any help on clearing up my misunderstanding of how this functions would be appreciated.

Head First Design Patterns 中,有一个很好的线程安全单例示例,它使用 "code magnets",您可以在其中考虑两个线程执行相同代码的所有可能方式。它使用三列完成,一列用于两个线程中的每一个,第三列用于返回假定的单例值。这是一个练习,您可以垂直放置代码片段以显示两个线程之间的操作顺序。我将尝试在 SO 中使用有限的格式和您的代码示例在此处重现它。

代码片段(没有锁)是:

get{
    if (instance == null){
        instance = 
            new Singleton(); }
    return instance; }

由于线程执行的方式,您可以找到一种可能导致返回 class 的两个实例的执行:

Thread One                   Thread Two                       Value instance
get{                                                          null
                             get{                             null
    if (instance == null){                                    null
                                 if (instance == null){       null
        instance = 
            new Singleton(); }                                Object_1
    return instance; }                                        Object_1
                                     instance = 
                                           new Singleton(); } Object_2
                                 return instance; }           Object_2

get { 之后 lockThread Two 将无法继续(如上所述),直到 Thread One 执行了 return instance;并释放锁:

Thread One                   Thread Two                       Value instance
get{ [takes lock]                                             null
                             get{ [blocks on lock]            null
    if (instance == null){                                    null
        instance = 
            new Singleton(); }                                Object_1
    return instance; } [releases lock]                        Object_1
                                 [continues] 
                                 if (instance == null) {      Object_1
                                 return instance; }           Object_1