创建型-单例模式 SingletonPattern

单例模式 Singleton

  • 保证一个类只有一个实例的实现方法
  • 给其他类提供一个全局的访问点。
  • 由自己创建自己的唯一实例

实现

  • 实现方法分为饿汉式(线程安全)、懒汉式(线程不安全)、懒汉式(lock+双重验证、线程安全)、延迟加载(Lazy、线程安全)

1.饿汉式

这种方式比较常用,但容易产生垃圾对象.这时候初始化 instance 显然没有达到 lazy loading 的效果。
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。

public class EagerSingleton
{
    private EagerSingleton() { }
    private static readonly EagerSingleton Instance = new EagerSingleton();

    public static EagerSingleton GetInstance()
    {
        return Instance;
    }
}

2.最简单的实现:懒汉式(线程不安全)

namespace Singleton
{
    public class Singleton
    {
        private static Singleton _uniqueInstance;

        private Singleton()
        {
        }

        public static Singleton GetInstance()
        {
            if (_uniqueInstance is null)
            {
                _uniqueInstance = new Singleton();
            }

            return _uniqueInstance;
        }
    }
}

定义了一个静态方法,作为全局访问点,在单线程下是正常的,在多线程同时运行GetInstance,得到的_uniqueInstance都是null,此时就会创建多个 定义了一个静态方法,作为全局访问点,在单线程下是正常的,在多线程同时运行GetInstance,得到的_uniqueInstance都是null,此时就会创建多个的实例。

多线程访问得到hash code是不一样的。

static void Main(string[] args)
{

    Task.Run(() =>
    {
        Singleton singleton = Singleton.GetInstance();
        Console.WriteLine(singleton.GetHashCode());
    });

    Task.Run(() =>
    {
        Singleton singleton = Singleton.GetInstance();
        Console.WriteLine(singleton.GetHashCode());
    });

    Console.WriteLine("over!");
}

over还提前输出。

over!
4032828
6044116
static void Main(string[] args)
{
    Singleton singleton1 = Singleton.GetInstance();
    Console.WriteLine(singleton1.GetHashCode());

    Singleton singleton2 = Singleton.GetInstance();
    Console.WriteLine(singleton2.GetHashCode());

    Console.WriteLine("over!");
}

输出

58225482
58225482
over!

3.懒汉式(lock+双重验证、线程安全)

lock关键字

MSDN介绍

lock 关键字可确保当一个线程位于代码的临界区时,另一个线程不会进入该临界区。 如果其他线程尝试进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。
lock 关键字在块的开始处调用 Enter,而在块的结尾处调用 Exit。 ThreadInterruptedException 引发,如果 Interrupt 中断等待输入 lock 语句的线程。
通常,应避免锁定 public 类型,否则实例将超出代码的控制范围。

常见的结构 lock (this)、lock (typeof (MyType)) 和 lock ("myLock") 违反此准则:
如果实例可以被公共访问,将出现 lock (this) 问题。
如果 MyType 可以被公共访问,将出现 lock (typeof (MyType)) 问题。
由于进程中使用同一字符串的任何其他代码都将共享同一个锁,所以出现 lock("myLock") 问题。
最佳做法是定义 private 对象来锁定, 或 private static 对象变量来保护所有实例所共有的数据。
在 lock 语句的正文不能使用 等待 关键字。

最常使用的锁是如下格式的代码段:

private static object objlock = new object();
lock (objlock )
{
    //要执行的代码逻辑
}

使用lock关键字解决多线程问题

public static LockSingleton GetInstance()
{
    // 当第一个线程运行到这里时,此时会对locker对象 "加锁",
    // 当第二个线程运行该方法时,首先检测到locker对象为"加锁"状态,该线程就会挂起等待第一个线程解锁
    // lock语句运行完之后(即线程运行完之后)会对该对象"解锁"
    lock (Locker)
    {
        // 如果类的实例不存在则创建,否则直接返回
        if (_uniqueInstance == null)
        {
            _uniqueInstance = new LockSingleton();
        }
    }
    return _uniqueInstance;
}

在lock之前判断是否实例

上面的代码还可以优化,通过判断对象是否为null,如果不是null,则直接返回,否则先锁,然后再生成实例,保证不同线程访问得到的是一个实例

public static LockSingleton GetInstance()
{
    // 双重锁定只需要一句判断就可以了
    if (_uniqueInstance == null)
    {
        lock (Locker)
        {
            if (_uniqueInstance == null)
            {
                _uniqueInstance = new LockSingleton();
            }
        }
    }
    return _uniqueInstance;
}

3.使用lock

Task.Run(() =>
{
    LockSingleton lockSingleton = LockSingleton.GetInstance();
    Console.WriteLine(lockSingleton.GetHashCode());

});

Task.Run(() =>
{
    LockSingleton lockSingleton = LockSingleton.GetInstance();
    Console.WriteLine(lockSingleton.GetHashCode());
});

输出结果

over!
6044116
6044116

延迟加载(Lazy)

public class LazySingleton
{
    private static readonly Lazy<LazySingleton> SingletonLazy = new Lazy<LazySingleton>(() => new LazySingleton());

    /// <summary>
    /// 私有构造函数
    /// </summary>
    private LazySingleton()
    {
        Console.WriteLine("我被创建了.Lazy");
    }

    /// <summary>
    /// 获取实例
    /// </summary>
    /// <returns></returns>
    public static LazySingleton GetInstance()
    {
        return SingletonLazy.Value;
    }
}

总结

单例主要分为如下几种方式,在实际使用过程中:建议采用延迟加载(Lazy)

饿汉式 懒汉式 懒汉式+lock锁+双重判断 延迟加载(Lazy)
线程安全 线程不安全 线程安全 线程安全
不是延迟加载(会浪费内存) 会延迟加载 会延迟加载 会延迟加载
没有加锁 没有加锁 加锁 加锁
posted @ 2021-01-09 17:21  、天上月  阅读(246)  评论(0编辑  收藏  举报