勇哥注:
C# 多线程调用单例类时的安全性问题是非常值得我们重视的,出现类似的问题会造成软件出现偶发问题,非常隐蔽。
单例类造成的问题主要有两方面: 一是你的单例到底是不是单例? 二是你的单例是不是在被多线程调用。
第一个问题请参考:
C#实现单例模式的几种方法总结 http://47.98.154.65/?id=1924
第二个问题请参考下面的说明:
多个线程,同时调用单例的同一个方法会出现什么现象? 阻塞? 并不会!!! 属性是安全的? 也不是!!! 总结就是: 多线程调用单例的同一个方法 线程不会阻塞,数据也不安全
普通的单例模式是线程不安全的,验证方法如下:
sealed class Singleton1 { private Singleton1() { } private static Singleton1 instance = null; public static Singleton1 Instance { get { if (instance == null) { Console.WriteLine("Cup"); instance = new Singleton1(); } return instance; } } } class Program { static void Main(string[] args) { Singleton1 st1 = null; Singleton1 st2 = null; while (true) { Thread t1 = new Thread(() => { st1 = Singleton1.Instance; }); Thread t2 = new Thread(() => { st2 = Singleton1.Instance; }); t1.Start(); t2.Start(); Thread.Sleep(100); } } }
如上所示,打印的结果有很大概率出现两次"Cup",说明两个线程都创建了新的对象,单例被打破了。
改进方式:加锁
sealed class Singleton1 { private Singleton1() { } private static readonly object syncObj = new object(); private static Singleton1 instance = null; public static Singleton1 Instance { get { lock (syncObj) { if (instance == null) { Console.WriteLine("Cup"); instance = new Singleton1(); } } return instance; } } public void Clear() { instance = null; } }
运行结果只有一个"Cup",程序在进入代码段时首先判断有没有加锁,如果没有就加锁,另一个线程判断代码已经有锁了,就直接返回,从而保证了单例的唯一性。
缺点:判断锁状态和尝试加锁操作比较消耗性能
改进:锁前判断:
public static Singleton1 Instance { get { if (instance == null) { lock (syncObj) { if (instance == null) { Console.WriteLine("Cup"); instance = new Singleton1(); } } } return instance; } }
如此,就只有第一次试图创建实例时要加锁
有没有不用锁的办法呢,也有:
sealed class Singleton1 { private Singleton1() { } private static Singleton1 instance = new Singleton1(); public static Singleton1 Instance { get { if (instance != null) { return instance; } else { return null; } } } }
C#在调用静态构造函数时初始化静态变量,运行时能够确保只调用一次静态构造函数。但这种机制不确定在其他语言中也存在
如果局限于C#中,还有更优化的方法:
sealed class Singleton1 { private Singleton1() { } public static Singleton1 Instance { get { return Nested.instance; } } class Nested { static Nested() { } internal static readonly Singleton1 instance = new Singleton1(); } }
将创建实例的时机局限在获取Instance时。

