一、引言
自己也算是使用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,请大家能够给出。