单例模式混淆

本文关键字:单例模式 | 更新日期: 2023-09-27 18:18:50

根据单例模式,

public sealed class Singleton
{
    static Singleton instance=null;
    Singleton()
    {
    }
    public void abc(){
    }
    public static Singleton Instance
    {
        get
        {
            if (instance==null)
            {
                instance = new Singleton();
            }
            return instance;
        }
    }
}
上面的

不是线程安全的。两个不同的线程都可以计算测试if (instance==null)并发现它为真,然后都创建实例,这违反了单例模式。

混淆是实例是静态的,这怎么可能是空的,一旦它被调用UI线程或其他线程?

编辑

我的意思是说一旦我调用了Singleton.Instance.abc();单例。实例在被手动处置之前不应该为空。对吧?

单例模式混淆

控制权传递给ThreadA

ThreadA试图得到Instance,发现是null

控制传递给ThreadB

ThreadB试图得到Instance,它被发现是null

控制传递给ThreadA

ThreadA实例化了Instance

控制权传递给ThreadB

ThreadB重新实例化Instance

解决方案:您可以使用static构造函数来确保不会发生这种情况。

您是正确的,您所显示的单例不是线程安全的。使其线程安全的简单方法是:

public sealed class Singleton
{
    static Singleton instance=null;
    static lockObject = new object();
    Singleton()
    {
    }
    public static Singleton Instance
    {
        get
        {
            lock(lockObject)
            {
               if (instance==null)
               {
                   instance = new Singleton();
               }
            }
            return instance;
        }
    }
}

你可以编写一个没有锁的版本线程安全,利用c#使用静态(http://www.yoda.arachsys.com/csharp/singleton.html)

public sealed class Singleton
{
    static readonly Singleton instance=new Singleton();
    // Explicit static constructor to tell C# compiler
    // not to mark type as beforefieldinit
    static Singleton()
    {
    }
    Singleton()
    {
    }
    public static Singleton Instance
    {
        get
        {
            return instance;
        }
    }
}

最后一个版本利用了这样一个事实:c#中的静态构造函数被指定为仅在创建类的实例或引用静态成员时执行,每个AppDomain执行一次。

编辑:

我的意思是说,一旦我调用了Singleton.Instance.abc();单例。实例在被手动处置之前不应该为空。对吧?

一旦一个线程被用来访问Singleton,它就已经被初始化了,任何你在调用之后启动的线程都可以安全地访问Singleton,而不需要它是null

如果你同时启动多个访问Singleton的线程,而让代码保持原样,你就会遇到问题。如果您不使用线程同步(lock)来确保if null块一次只输入一个线程,那么您将有一个竞争条件。

如果多个线程偷偷通过if块,那么多个线程将尝试初始化instance。这是混乱的,而且很难预测到底会发生什么。一个问题是,您实际上可以获得多个实例,每个偷偷通过的线程一个实例。任何后续调用(在竞争条件之后)都将获得创建的最后一个实例。

这可能不是您将看到的最糟糕的问题。可能会有更严重的问题导致崩溃(不确定)。最好对Singleton使用线程安全的操作,而不是找出:)

关于如何使你的Singleton线程安全,请参阅下面我的原始答案:


可以在静态构造函数中创建实例。

静态构造函数只在第一次访问时调用,因此不需要惰性初始化代码。

public sealed class Singleton
{
    private static Singleton instance;
    private Singleton() { }
    static Singleton()
    {
        instance = new Singleton();
    }
    public static Singleton Instance
    {
        get { return instance; }
    }
}

根据这个问题的答案,它保证只被调用一次,并且是线程安全的:

静态构造函数保证在每个应用程序域只运行一次,在创建类的任何实例或访问任何静态成员之前。http://msdn.microsoft.com/en-us/library/aa645612.aspx

所示的实现对于初始构造是线程安全的,也就是说,在构造Singleton对象时不需要锁定或空测试。然而,这并不意味着对实例的任何使用都将被同步。有很多方法可以做到这一点;我在下面展示了一个。

只有一种情况下,它不是线程安全的:如果它在两个不同的线程中同时运行,而之前没有初始化。

两个线程都将运行Instance(),然后可能发生在其中一个能够分配新引用之前,两个线程都计算instance==null

要避免这种情况,请使用Shadow Wizard的方法,或者确保在启动多个线程之前调用它一次(当然,这要复杂得多,也容易出错)。

斯基特先生在他的网站上有一篇关于这方面的文章。

http://csharpindepth.com/Articles/General/Singleton.aspx

编辑

好的,所以问题已经被问到,静态调用如何创建2个实例,而静态意味着只有1个。

当在单独的线程上执行时,代码是共享的,但代码的状态不是。在这种情况下,重要的是执行堆栈。下面是一个例子。

线程1和线程2同时执行Instance getter。

线程1计算if (instance==null)为true,并分支到if代码。
线程2将if (instance==null)计算为true并分支到if代码。

线程1现在设置instance为一个新的单例对象。
线程1返回instance

线程2现在设置instance为一个新的单例对象。记住,线程2之前已经计算了条件,并且已经分支到这段代码中。
然后线程2返回instance

在这个场景中,我们返回了2个Singleton实例。

您的示例不是线程安全的,但是实际获得竞争条件的概率非常小,因此在许多情况下这并不重要。

如果线程1计算instance,线程2紧随其后,并且在线程1完成创建单例对象并将其分配给instance之前计算instance,线程2仍然会创建一个新对象,并将其分配给instance

由于单例最常见的用例是某种设置或配置对象,所以这无关紧要(它不会导致应用程序失败)。但在其他用例中,这可能很重要。

编辑:

在回答你的编辑:第一个实例可以由GC一旦线程2创建第二个实例并将其分配给你的单例类instance变量,从那时起第一个实例将不再被你的应用程序引用。