单身模式混乱

时间:2011-12-25 10:33:36

标签: c#

按照单身模式,

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线程或其他线程上调用它,它如何为null?

修改

我的意思是说,一旦我调用了Singleton.Instance.abc(); Singleton.Instance在手动处理之前不应为null。正确?

6 个答案:

答案 0 :(得分:5)

控制权转移到ThreadA

ThreadA试图获取Instance,发现它是null

控制权转移到ThreadB

ThreadB试图获取Instance,发现它是null

控制权转移到ThreadA

ThreadA实例化Instance

控制权转移到ThreadB

ThreadB重新实例化Instance

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

答案 1 :(得分:2)

你说的是你所显示的单身不是线程安全的。使线程安全的简单方法是:

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一次。

答案 2 :(得分:2)

编辑:

  

我的意思是说,一旦我调用了Singleton.Instance.abc(); Singleton.Instance在手动处理之前不应为null。正确?

一旦使用单个线程访问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; }
    }
}

根据the answer to this question,它保证只被调用一次,并且是线程安全的:

  

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

     

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

答案 3 :(得分:0)

斯基特先生在他的网站上有一篇关于此事的文章。

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设置为一个新的Singleton对象 线程1然后返回instance

线程2现在将instance设置为新的Singleton对象。请记住,线程2已经提前评估了这个条件,并已分支到此代码中 线程2然后返回instance

在这种情况下,我们返回了2个Singleton实例。

答案 4 :(得分:0)

只有一个时刻它不会是线程安全的:如果在两个不同的线程中调用它而在之前没有被初始化的同时运行。

两个线程都将运行Instance(),然后可能会在任何一个线程都能够分配新引用之前评估instance==null

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

答案 5 :(得分:-1)

你的例子不是线程安全的,但实际上获得竞争条件的可能性非常小,所以在很多情况下它并不重要。

如果线程1评估instance,并且线程2紧跟在后面,并在线程1完成创建单例对象并将其分配给instance之前评估instance,则线程2仍将创建一个新对象,并将其分配给instance

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

修改 回答您的编辑:一旦线程2创建第二个实例并将其分配给您的单件类instance变量,第一个实例可由GC处理,从那时起第一个实例您的申请将不再被引用。