C#中的Singleton模式

最简单的实现方法自然就是按照UML类图直接写一个类,我们看看代码。

class Program
{
    static void Main(string[] args)
    {
    	var single1 = Singleton.Instance;
        var single2 = Singleton.Instance;
        Console.WriteLine(object.ReferenceEquals(single1, single2));
        Console.ReadLine();
    }
}

class Singleton
{
    private static Singleton _Instance = null;
    private Singleton()
    {
        Console.WriteLine("Created");
    }

    public static Singleton Instance
    {
        get
        {
            if (_Instance == null)
            {
                _Instance = new Singleton();
            }
            return _Instance;
        }
    }

    public void DumbMethod()
    {

    }
}

这段代码忠实的实现了UML类图里面的一切,查看输出结果,

证实了Singleton确实起了作用,多次调用仅仅产生了一个实例,似乎这么写就可以实现这个模式了。但是,真的会那么简单吗?

如果多线程乱入?
现在我们给刚刚的例子加点调料,假设多个对实例的调用,并不是简单的,彬彬有礼的顺序关系,二是以多线程的方式调用,那么刚刚那种实现方法,还能从容应对吗?让我们试试。把Main函数里面的调用改成这样。

static void Main(string[] args)
    {
        int TOTAL = 10000;
        Task[] tasks = new Task[TOTAL];
        for (int i = 0; i < TOTAL; i++)
        {
            tasks[i] = Task.Factory.StartNew(() =>
            {
                Singleton.Instance.DumbMethod();
            });
        }
		Task.WaitAll(tasks);
        Console.ReadLine();
    }

通过Factory创造出1万个Task,几乎同时去请求这个单例,看看输出。

咦,我们刚刚写的Singleton模式失效了,这个类被创造了5次(这段代码运行多次,这个数字不一定相同),一定是多线程搞的鬼,我们刚刚写的代码没有办法应对多线程,换句话说,是非线程安全的(thread-safe),那有没有办法来攻克这个难关呢?

线程安全的单例模式
Lock版本
提到线程安全,很多同学第一反应就是用lock,不错,lock是个可行的办法,让我们试试。添加一个引用类型的对象作为lock对象,修改代码如下(什么?你问我为什必须是引用类型的对象而不能是值类型的对象?因为lock的时候,如果对象是值类型,那么lock仅仅锁住了它的一个副本,另外一个线程可以畅通无阻的再次lock,这样lock就失去了阻塞线程的意义)

private static object _SyncObj = new object();
    public static Singleton Instance
    {
        get
        {
            lock (_SyncObj)
            {
                if (_Instance == null)
                {
                    _Instance = new Singleton();
                }
                return _Instance;
            }                
        }
    }

运行一下,输出

只有一个实例创建,证明Lock起作用了,这个模式可行!不过有些不喜欢用Lock的同学可能要问,还有没有其他办法呢?答案是有的。

静态构造器版本
回想一下,C#中的类静态构造器,只会在这个类第一次被使用的时候调用一次,天然的线程安全,那我们试试不用Lock使用类静态构造器?修改Singleton类如下:

class Singleton
{
    private static Singleton _Instance = null;
    private Singleton()
    {
        Console.WriteLine("Created");
    }

    static Singleton()
    {
        _Instance = new Singleton();
    }

    //private static object _SyncObj = new object();
    public static Singleton Instance
    {
        get { return _Instance; }
    }

    public void DumbMethod()
    {

    }
}

去掉了Lock,添加了一个类静态构造器,试一试。

完美!对于不喜欢用Lock(在这个例子中,实例只会创建一次但是之后的所有线程都要先排队Lock再进入Critical code进行检查,效率比较低下)的同学,类静态构造器提供了一种很好的选择。
不过俗话说,人心苦不足 , 我们总是追求卓越。这个版本比Lock版本似乎更好一点,那还有没有更好的版本呢?有的。

Lazy版本
从net 4.0开始,C#开始支持延迟初始化,通过Lazy关键字,我们可以声明某个对象为仅仅当第一次使用的时候,再初始化,如果一直没有调用,那就不初始化,省去了一部分不必要的开销,提升了效率。如果你不熟悉Lazy或者想更多了解它,请参考。我们今天关注的重点在于,Lazy也是天生线程安全的,所以我们尝试用它来实现Singleton模式?修改代码如下:

class Singleton
{
    private static Lazy<Singleton> _Instance = new Lazy<Singleton>(() => new Singleton());
    private Singleton()
    {
        Console.WriteLine("Created");
    }

    public static Singleton Instance
    {
        get
        {
            return _Instance.Value;
        }
    }

    public void DumbMethod()
    {

    }
}
 亚马逊测评 www.yisuping.com
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值