• 23种设计模式之单例(Singleton Pattern)


    单例

    在软件系统中,经常有这样一些特殊的类,必须保证它们在系统中只存在一个实例(eg:应对一些特殊情况,比如数据库连接池(内置了资源)  全局唯一号码生成器),才能确保它们的逻辑正确性、以及良好的效率。

    优点:单例的好处就是单例,就是全局唯一的一个实例
    单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例

    缺点:单例可以避免重复创建,但是也会常驻内存 除非是真的有必要,否则不要单例

    使用单例对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用new关键字实例化对象。因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自己无法直接实例化此类

    如何绕过常规的构造器,提供一种机制来保证一个类只创建一个实例?

    如何实现?将构造函数私有化,然后对外提供一个公开的静态方法,使用一个静态属性进行判断当前对象是否被创建

     1 // 不要用这种方式
     2 public class Singleton
     3 {
     4     private static Singleton _instance = null;
     5     private Singleton() { }
     6     public static Singleton CreateInstance()
     7     {
     8         if (_instance == null)
     9         {
    10             _instance = new Singleton();
    11         }
    12         return _instance;
    13     }
    14 }
    View Code

    上面的方法是非线程安全的,多个不同的线程可以同时进入这个方法,如果instance为空的并且这里返回真的情况下,都可以创建实例,这显然违反了单例模式,实际上,在测试以前,实例就已经有可能被创建了,但是内存模型不能保证这个实例能被其他的线程看到,除非合适的内存屏障已经被跨过了

    我们把上面的代码优化一下

     1     /// <summary>
     2     /// 单例类:一个构造对象很耗时耗资源类型
     3     /// 懒汉式单例模式
     4     /// </summary>
     5     public class Singleton
     6     {
     7         /// <summary>
     8         /// 构造函数耗时耗资源
     9         /// </summary>
    10         private Singleton()
    11         {
    12             long lResult = 0;
    13             for (int i = 0; i < 10000000; i++)
    14             {
    15                 lResult += i;
    16             }
    17             Thread.Sleep(2000);
    18             Console.WriteLine("{0}被构造一次", this.GetType().Name);
    19         }
    20         /// <summary>
    21         /// 3 全局唯一静态  重用这个变量
    22         /// </summary>
    23         private static volatile Singleton _Singleton = null;
    24         //volatile 促进线程安全 让线程按顺序操作
    25         private static readonly object Singleton_Lock = new object();
    26         /// <summary>
    27         /// 2 公开的静态方法提供对象实例
    28         /// </summary>
    29         /// <returns></returns>
    30         public static Singleton CreateInstance()
    31         {
    32             if (_Singleton == null)//是_Singleton已经被初始化之后,就不要进入锁等待了
    33             {
    34                 lock (Singleton_Lock)
    35                 //保证任意时刻只有一个线程进入lock范围
    36                 //也限制了并发,尤其是_Singleton已经被初始化之后
    37                 {
    38                     //Thread.Sleep(1000);
    39                     //Console.WriteLine("等待锁1s之后才继续。。。");
    40                     if (_Singleton == null)//保证只实例化一次
    41                     {
    42                         _Singleton = new Singleton();
    43                     }
    44                 }
    45             }
    46             return _Singleton;
    47         }
    48 
    49         //既然是单例,大家用的是同一个对象,用的是同一个方法,那还会并发吗  还有线程安全问题吗?
    50         public int iTotal = 0;
    51         public void Show()
    52         {
    53             //lock (Singleton_Lock)
    54             //{
    55                 this.iTotal++;
    56             //}
    57         }
    58 
    59         public static void Test()
    60         {
    61             Console.WriteLine("Test1");
    62             Console.WriteLine(_Singleton.iTotal);
    63         }
    64 
    65     }
    View Code

    前端调用

     1                {
     2                     List<Task> tasks = new List<Task>();
     3                     for (int i = 0; i < 10000; i++)
     4                     {
     5                         tasks.Add(Task.Run(() =>
     6                         {
     7                             Singleton singleton = Singleton.CreateInstance();
     8                             singleton.Show();
     9                         }));
    10                     }
    11                     Task.WaitAll(tasks.ToArray());
    12                     Singleton.Test();
    13                     //iTotal 是0  1   10000  还是其他的
    14                     //其他值,1到10000范围内都可能   线程不安全
    15 
    16                 }
    View Code

     运行代码我们会发现一个问题

    iTotal 是0  1   10000  还是其他的,
    其他值,1到10000范围内都可能   线程不安全
    为什么呢?造成这种情况的原因单例执行singleton.Show()方法时 iTotal在等于某个值时被附加多次,由此得到结论:
    即使是单例,变量也不是线程安全的,单例不是为了保证线程安全
    如何优化?给show方法加把锁
    1         public void Show()
    2         {
    3             lock (Singleton_Lock)
    4             {
    5                 this.iTotal++;
    6             }
    7         }
    View Code

    单例还有另外的写法,以上是懒汉式单例模式,下面我们来看看饿汉式

    利用静态构造函数 程序第一次使用这个类型前被调用,且只调用一次

     1     /// <summary>
     2     /// 单例类:一个构造对象很耗时耗资源类型
     3     /// 
     4     /// 饿汉式
     5     /// </summary>
     6     public class SingletonSecond
     7     {
     8         /// <summary>
     9         /// 1 构造函数耗时耗资源
    10         /// </summary>
    11         private SingletonSecond()
    12         {
    13             long lResult = 0;
    14             for (int i = 0; i < 10000000; i++)
    15             {
    16                 lResult += i;
    17             }
    18             Thread.Sleep(1000);
    19             Console.WriteLine("{0}被构造一次", this.GetType().Name);
    20         }
    21         /// <summary>
    22         /// 静态构造函数:由CLR保证,程序第一次使用这个类型前被调用,且只调用一次
    23         /// 
    24         /// 检测,初始化
    25         /// 写日志功能的文件夹检测
    26         /// XML配置文件
    27         /// </summary>
    28         static SingletonSecond()
    29         {
    30             _SingletonSecond = new SingletonSecond();
    31             Console.WriteLine("SingletonSecond 被启动");
    32         }
    33 
    34 
    35         private static SingletonSecond _SingletonSecond = null;
    36         public static SingletonSecond CreateInstance()
    37         {
    38             return _SingletonSecond;
    39         }//饿汉式  只要使用类就会被构造
    40 
    41         
    42     }
    View Code

    另外一种类似的,利用静态字段创建对象

     1     /// <summary>
     2     /// 单例类:一个构造对象很耗时耗资源类型
     3     /// 饿汉式
     4     /// </summary>
     5     public class SingletonThird
     6     {
     7         /// <summary>
     8         /// 构造函数耗时耗资源
     9         /// </summary>
    10         private SingletonThird()
    11         {
    12             long lResult = 0;
    13             for (int i = 0; i < 10000000; i++)
    14             {
    15                 lResult += i;
    16             }
    17             Thread.Sleep(1000);
    18             Console.WriteLine("{0}被构造一次", this.GetType().Name);
    19         }
    20 
    21         /// <summary>
    22         /// 静态字段:在第一次使用这个类之前,由CLR保证,初始化且只初始化一次
    23         /// 这个比今天构造函数还早
    24         /// </summary>
    25         private static SingletonThird _SingletonThird = new SingletonThird();//打印个日志
    26         public static SingletonThird CreateInstance()
    27         {
    28             return _SingletonThird;
    29         }//饿汉式  只要使用类就会被构造
    30 
    31 
    32 
    33 
    34         public void Show()
    35         {
    36             Console.WriteLine("这里是{0}.Show", this.GetType().Name);
    37         }
    38 
    39     }
    View Code

     本文参考文档:https://csharpindepth.com/Articles/Singleton#unsafe

  • 相关阅读:
    RK3288 Android5.1系统编译
    RK3288 模块单独编译
    ubuntu 设置固定DNS
    PHP取整函数之ceil,floor,round,intval的区别
    PHP实现 APP端微信支付功能
    Laravel中服务提供者和门面模式
    laravel项目利用twemproxy部署redis集群的完整步骤
    PHP转盘抽奖算法
    laravel中短信发送验证码的实现方法
    PHP操作Redis常用
  • 原文地址:https://www.cnblogs.com/Dewumu/p/11433682.html
Copyright © 2020-2023  润新知