本系列将描述主要的设计模式,使用C#代码进行演示,今天展示的是最简单的单例模式。
单例模式,顾名思义,就是类的实例仅有一个。比如天上的月亮,仅只有1个,不支持使用生成(new)多个,那么我们最简单的写法就是私有化构造函数,然后new一个实例,所有获取实例都通过属性来获取,演示代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CSharpDesignPattern.Singleton
{
    /// <summary>
    /// 月亮(单例)使用双重锁定
    /// </summary>
    public class Moon
    {
        private static Moon _instance=null;
        //private static object lockObj = new object();

        /// <summary>
        /// 私有构造函数,避免调用方初始化
        /// </summary>
        private Moon() 
        {
        }

        /// <summary>
        /// 外部访问实例
        /// </summary>
        public static Moon Instance 
        { 
            get 
            {
                if (_instance == null)
                {
                    _instance=new Moon();
                }
                return _instance; 
            } 
        }

    }
}

以上代码看上很好,但是在多线程下存在问题,比如第一个线程进入new Moon对象,此时还没有完成,第二个线程已经在获取Moon实例,此时会报未将对象引用设置实例,测试代码如下:

using CSharpDesignPattern.Singleton;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace CSharpDesignPattern.Test.Singleton
{
    [TestClass]
    public class MoonUnitTest
    {
        [TestMethod]
        public void IsMoonSingleton()
        {
            var listTask = new List<Task>();
            ConcurrentDictionary<int, int> dic = new ConcurrentDictionary<int, int>();
            for (int i = 0; i < 100; i++)
            {
                var task = Task.Factory.StartNew(() =>
                {
                    var instance = Moon.Instance;
                    dic[instance.GetHashCode()] = i;

                });
                listTask.Add(task);
            }
            Task.WaitAll(listTask.ToArray());

            Assert.AreEqual(1, dic.Count);
        }
    }
}

断言失败,我们预期生成一个实例,但是在多线程下,生成了2个实例,一不小心,变成了2个月亮
image.png
那么如何确保在多线程下正确呢,这就要使用锁来保护,让new动作变成串行,我们将初始化代码修改如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CSharpDesignPattern.Singleton
{
    /// <summary>
    /// 月亮(单例)使用双重锁定
    /// </summary>
    public class Moon
    {
        private static Moon _instance = null;
        private static object lockObj = new object();

        /// <summary>
        /// 私有构造函数,避免调用方初始化
        /// </summary>
        private Moon()
        {
        }

        /// <summary>
        /// 外部访问实例
        /// </summary>
        public static Moon Instance
        {
            get
            {
                if (_instance == null)
                {
                    lock (lockObj)
                    {
                        if (_instance == null)
                        {
                            _instance = new Moon();
                        }                        
                    }
                }
                return _instance;
            }
        }

    }
}

修改后,单元测试顺利通过
image.png

当然,我们可以使用更简单的方式,使用static readonly保证类初始化为单例,也是可以顺利通过单元测试的,实例代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CSharpDesignPattern.Singleton
{
    /// <summary>
    /// 太阳(单例),通过readonly方式构建
    /// </summary>
    public class Sun
    {
        private static readonly Sun _instance = new Sun();

        /// <summary>
        /// 私有构造函数,避免调用方初始化
        /// </summary>
        private Sun() { }

        /// <summary>
        /// 外部访问实例
        /// </summary>
        public static Sun Instance { get { return _instance; } }


    }
}

Wittgenstein
1 声望0 粉丝