• 创建型-单例模式 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)
    线程安全 线程不安全 线程安全 线程安全
    不是延迟加载(会浪费内存) 会延迟加载 会延迟加载 会延迟加载
    没有加锁 没有加锁 加锁 加锁
  • 相关阅读:
    声明
    Random类——获取随机数
    JDK_API的使用方法
    Ajax?有谁开始学习了吗?
    用xslt循环xml同一节点的不同子节点
    在Repeater控件中嵌套一个Repeater控件
    html+css的一些技巧.收集中...
    记录一下: onbeforeunload()方法, 避免未保存而关闭页面.
    简单的C# Socket编程
    不实用的UriBuilder类
  • 原文地址:https://www.cnblogs.com/igeekfan/p/Singleton-Pattern.html
Copyright © 2020-2023  润新知