zoukankan      html  css  js  c++  java
  • 创建型-单例模式 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)
    线程安全 线程不安全 线程安全 线程安全
    不是延迟加载(会浪费内存) 会延迟加载 会延迟加载 会延迟加载
    没有加锁 没有加锁 加锁 加锁
  • 相关阅读:
    asr相关技术总结
    SLURM 使用基础教程
    FSMN 及其变种 cFSMN DFSMN pyramidal-FSMN
    均方根误差(RMSE),平均绝对误差 (MAE),标准差 (Standard Deviation)
    linux文本编码格式转化 字幕处理
    PyTorch-Kaldi 语音识别工具包
    SRILM Ngram 折扣平滑算法
    awk 调用 shell 命令,并传递参数
    用 scikit-learn 和 pandas 学习线性回归
    逻辑回归 及 实例
  • 原文地址:https://www.cnblogs.com/igeekfan/p/Singleton-Pattern.html
Copyright © 2011-2022 走看看