这不是一种更简单,更安全(也就是更好)的方法来实现单例而不是双重检查锁定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() { }
}
编辑:线程安全测试失败,任何人都可以解释原因吗?为什么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
答案 0 :(得分:10)
如果您的单身人士有多次初始化的危险,那么您的问题会更严重。为什么不直接使用:
public class Singleton
{
private static Singleton instance=new Singleton();
private Singleton() {}
public static Singleton Instance{get{return instance;}}
}
在初始化方面绝对是线程安全的。
编辑:如果我不清楚,你的代码非常 错误 。 if
检查和new
都 不 线程安全!你需要使用一个合适的单例类。
答案 1 :(得分:8)
您可能正在创建多个实例,但这些实例会被垃圾收集,因为它们不会在任何地方使用。在任何情况下,静态_instance字段变量都不会多次更改其值,即从null变为有效值的单次时间。因此,尽管已经创建了多个实例,但此代码的使用者将只看到相同的实例。
锁定免费编程
Joe Duffy,在他的名为 Windows上的并发编程的书中实际上分析了你试图在第10章,内存模型和锁定自由,第526页上使用的这种模式。
他将此模式称为松散引用的延迟初始化:
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;
}
}
}
正如我们所看到的,在上面的示例中,方法是免费锁定的,其代价是创建一次性对象。在任何情况下,Value属性都不会因此类API的使用者而改变。
平衡权衡
锁定自由需要付出代价,并且需要谨慎选择权衡取舍。在这种情况下,锁定自由的代价是您必须创建不会使用的对象实例。这可能是一个可接受的支付价格,因为你知道通过免于锁定,死锁风险和线程争用的风险较低。在这个特定的实例中,单例的语义本质上是创建一个对象的单个实例,所以我更愿意选择{{3正如@Centro在他的回答中引用的那样。
然而,当 我们使用Interlocked.CompareExchange
时,它仍然存在问题?我喜欢你的例子,这是非常发人深省的,当@Blindy引用它不是可怕错误时,许多人很快就把它误解为错误。
这一切归结为你是否计算了权衡并决定:
只要你意识到权衡取舍,并且有意识地决定创建新对象以获得无锁,那么你的例子也可以是一个可以接受的答案。
答案 2 :(得分:6)
为了不使用'双重检查锁定mambo-jambo'或者根本不使用自己的单例重新发明轮子,请使用.NET 4.0中包含的现成解决方案 - Lazy<T>。
答案 3 :(得分:3)
public class Singleton
{
private static Singleton _instance = new Singleton();
private Singleton() {}
public static Singleton Instance
{
get
{
return _instance;
}
}
}
答案 4 :(得分:3)
我不相信你完全可以相信。是的,Interlocked.CompareExchanger是原子的,但是新的Singleton()在任何非平凡的情况下都不会是原子的。由于在交换值之前必须进行评估,因此这通常不是线程安全的实现。
答案 5 :(得分:3)
这是怎么回事?
public sealed class Singleton
{
Singleton()
{
}
public static Singleton Instance
{
get
{
return Nested.instance;
}
}
class Nested
{
// Explicit static constructor to tell C# compiler
// not to mark type as beforefieldinit
static Nested()
{
}
internal static readonly Singleton instance = new Singleton();
}
}
这是此页面上的第五个版本: http://www.yoda.arachsys.com/csharp/singleton.html
我不确定,但作者似乎认为它既是线程安全的又是延迟加载。
答案 6 :(得分:2)
您的单例初始化程序的行为完全符合预期。见Raymond Chen的Lock-free algorithms: The singleton constructor:
这是一个双重检查锁,但没有锁定。在进行初始构建时,我们只是让它成为一个免费的人来创建对象,而不是锁定。如果五个线程同时到达此代码,那么,让我们创建五个对象。在每个人创建他们认为是获胜对象之后,他们调用InterlockedCompareExchangePointerRelease来尝试更新全局指针。
当可以让多个线程尝试创建单例(并让所有输家都销毁他们的副本)时,这种技术是合适的。如果创建单例是昂贵的或有不必要的副作用,那么你不想使用free-for-all算法。
每个线程创建对象;因为它认为还没有人创造它。但是在InterlockedCompareExchange
期间,只有 一个 主题才能真正设置全局单例。
答案 7 :(得分:1)
这不是线程安全的。
您需要锁定才能将if()
和Interlocked.CompareExchange()
放在一起,然后您将不再需要CompareExchange
。
答案 8 :(得分:1)
你仍然有一个问题,你很可能正在创建和丢弃你的单身实例。执行Interlocked.CompareExchange()
时,无论赋值是否成功,都将始终执行Singleton
构造函数。因此,与你说的相比,你的情况并没有好转(或者更糟糕的是,恕我直言):
if ( _instance == null )
{
lock(latch)
{
_instance = new Singleton() ;
}
}
与线程争用相比,性能更好,而不是将lock
和测试的位置交换为null,但存在构造额外实例的风险。
答案 9 :(得分:1)
答案 10 :(得分:1)
自动属性初始化(C#6.0)似乎不会导致您看到的Singleton的多个实例化。
public class Singleton
{
static public Singleton Instance { get; } = new Singleton();
private Singleton();
}
答案 11 :(得分:0)
我认为.NET 4.0
使用System.Lazy<T>
之后最简单的方式:
public class Singleton
{
private static readonly Lazy<Singleton> lazy = new Lazy<Singleton>(() => new Singleton());
public static Singleton Instance { get { return lazy.Value; } }
private Singleton() { }
}
Jon Skeet有一篇很好的文章here,涵盖了很多实现单例的方法以及每个方法的问题。
答案 12 :(得分:0)
不要使用锁定。使用您的语言环境
大多数简单的线程安全实现是:
public class Singleton
{
private static readonly Singleton _instance;
private Singleton() { }
static Singleton()
{
_instance = new Singleton();
}
public static Singleton Instance
{
get { return _instance; }
}
}