一、引言
自己也算是使用Singleton的老客户了,也明白如何写代码能最大限度的避免多线程产生多个实例。但是自己却很少遇到在多线程下使用Singleton,所以自己也是一知半解。
今天正好有空,搞搞清楚这个问题。
二、问题的产生
请看如下代码:
public class Animal { private static Animal instance = null; private Animal() { } public static Animal getInstance() { if (instance == null) { instance = new Animal(); } return instance; } }
经常看到有些网友这样写。这样写在大都数情况下挺适用的。我们知道操作系统为每个独立线程安排一些CPU时间。单CPU操作系统以轮转方式向线程提供时间片,每个线程在使用完时间片后交出控制,系统再将CPU时间片分配给下一个线程,如下代码,我将使某个线程睡眠模拟线程的时间片用完。代码如下:
public static Animal getInstance() { if (instance == null) { if (Thread.CurrentThread.Name == "Thread1") { Thread.Sleep(2000);//模拟Thread1的时间片用完了 } instance = new Animal(); } return instance; }
private void BeginThread() { Thread th = new Thread(this.Run){IsBackground=true}; th.Name = "Thread1"; th.Start(); Thread th2 = new Thread(this.Run2) {IsBackground=true }; th2.Name="Thread2"; th2.Start(); } private void Run() { Animal p1 = People.Instance; } private void Run2() { Animal p2 = People.Instance; }
如上Animal p1与Animal p2就是不同的实例。
当然了,这里只是模拟一下时间片的轮换,虽然是模拟但是却能很好的说明问题,上面的Singleton有问题,那么应该怎么写呢?
我想有2个方法:
方法一:锁机制
public class Animal { private static Animal instance = null; private static object sync = new object(); private Animal() { } public static Animal getInstance() { if (instance != null) { lock (sync) { if (instance == null) { instance = new Animal(); } } } return instance; } }
我们可以通过锁住部分代码,同时间只让一个线程执行。有人要问了:为什么最外面还要用 "if (instance != null)"
因为同步操作执行时间长,也耗资源,在最外面加个判断能够更高效的实现Singleton.
值得注意的是:对于NET的执行机制,编译后再执行,编译会微调我们的代码,既然微调那么很可能执行的逻辑就会改变,为了严格意义上执行如上代码,可以加上volatile关键字
如:private static volatile Animal instance。
方法二:如果使用NET进行开发,可以借助于静态构造器的功能
public sealed class People { private People() { } public static readonly People Instance; static People() { Instance = new People(); } }
静态构造器自身就可以保证,多线程情况下,系统就可以保证只有一个执行。 使用readonly可以防止使用Instance=null设置实例。
当然了,NET还有其他机制可以避免伪Sigleton,请大家能够给出。