Singleton的简单实现

kat*_*roh 8 .net c# singleton multithreading

这不是一个更简单,更安全(因此更好)的方法来实现单例而不是双重检查锁定mambo-jambo?这种方法有什么缺点吗?


public class Singleton
{
    private static Singleton _instance;
    private Singleton() { Console.WriteLine("Instance created"); }

    public static Singleton Instance
    {
        get
        {
            if (_instance == null)
            {
                Interlocked.CompareExchange(ref _instance, new Singleton(), null);
            }
            return _instance;
        }
    }
    public void DoStuff() { }
}
Run Code Online (Sandbox Code Playgroud)

编辑:线程安全测试失败,谁能解释为什么?为什么Interlocked.CompareExchange不是真正的原子?


public class Program
{
   static void Main(string[] args)
   {
      Parallel.For(0, 1000000, delegate(int i) { Singleton.Instance.DoStuff(); });
   }
} 

Result (4 cores, 4 logical processors)
Instance created
Instance created
Instance created
Instance created
Instance created
Run Code Online (Sandbox Code Playgroud)

Bli*_*ndy 10

如果您的单身人士有多次初始化自身的危险,那么您的问题会更严重.为什么不使用:

public class Singleton
{
  private static Singleton instance=new Singleton();
  private Singleton() {}

  public static Singleton Instance{get{return instance;}}
}
Run Code Online (Sandbox Code Playgroud)

在初始化方面绝对是线程安全的.

编辑:如果我不清楚,你的代码是非常错误的.无论是if检查和new线程安全的!你需要使用一个合适的单例类.

  • @Streklin,http://msdn.microsoft.com/en-us/library/aa645758(v = vs.71).aspx (2认同)

Ana*_*yal 8

您可能正在创建多个实例,但这些实例将被垃圾收集,因为它们不会在任何地方使用.在任何情况下,静态_instance字段变量都不会多次更改其值,即从null变为有效值的单次时间.因此,尽管已经创建了多个实例,但此代码的使用者将只看到相同的实例.

锁定免费编程

Joe Duffy在他的书" Windows上的并发编程"实际上分析了你试图在第10章"内存模型和锁定自由"中使用的这种模式,第526页.

他将此模式称为轻松引用的Lazy初始化:

public class LazyInitRelaxedRef<T> where T : class
{
    private volatile T m_value;
    private Func<T> m_factory;

    public LazyInitRelaxedRef(Func<T> factory) { m_factory = factory; }


    public T Value
    {
        get
        {
            if (m_value == null) 
              Interlocked.CompareExchange(ref m_value, m_factory(), null);
            return m_value;
        }
    }

    /// <summary>
    /// An alternative version of the above Value accessor that disposes
    /// of garbage if it loses the race to publish a new value.  (Page 527.)
    /// </summary>
    public T ValueWithDisposalOfGarbage
    {
        get
        {
            if (m_value == null)
            {
                T obj = m_factory();
                if (Interlocked.CompareExchange(ref m_value, obj, null) != null && obj is IDisposable)
                    ((IDisposable)obj).Dispose();
            }
            return m_value;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

正如我们所看到的,在上面的示例中,方法是免费锁定的,其代价是创建一次性对象.在任何情况下,Value属性都不会因此类API的使用者而改变.

权衡利弊

锁定自由需要付出代价,并且需要谨慎选择权衡.在这种情况下,锁定自由的代价是您必须创建不会使用的对象实例.这可能是一个可接受的支付价格,因为你知道通过免于锁定,死锁风险和线程争用的风险较低.

然而,在这个特定的实例中,单例的语义本质上是创建一个对象的单个实例,所以我更愿意选择Lazy<T>@Centro在他的答案中引用.

尽管如此,它仍然存在问题,我们应该何时使用Interlocked.CompareExchange?我喜欢你的例子,这是非常发人深省的,很多人很快就把它误解为错误,因为@Blindy引用并不是非常错误.

这一切都归结为你是否计算了权衡并决定:

  • 你生产一个且只有一个实例有多重要?
  • 锁定免费有多重要?

只要你意识到权衡取舍并让它有意识地决定创建新对象以获得无锁,那么你的例子也可以是一个可以接受的答案.


Cen*_*tro 6

为了不使用'双重检查锁定mambo-jambo'或者根本不使用自己的单例重新发明轮子,请使用.NET 4.0中包含的现成解决方案 - Lazy <T>.