• 剑指offer之面试题2:实现Singleton模式


    来源:剑指offer

    这篇主要记录《剑指offer》书籍中的面试题2:实现Singleton模式

    使用语言:C# 

    代码环境:VS2017

    总共有5中解法,从前往后依次优化。

    结构如下:

    前言

    这里先给出调用程序的代码

    Program.cs

        class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine("Hello World!");
    
                //Task.Run(() =>
                //{
                //    Console.WriteLine(Singleton1.Instance);
                //});
                //Task.Run(() =>
                //{
                //    Console.WriteLine(Singleton1.Instance);
                //});
    
    
                //Task.Run(() =>
                //{
                //    Console.WriteLine(Singleton2.Instance);
                //});
                //Task.Run(() =>
                //{
                //    Console.WriteLine(Singleton2.Instance);
                //});
    
                //Task.Run(() =>
                //{
                //    Console.WriteLine(Singleton3.Instance);
                //});
                //Task.Run(() =>
                //{
                //    Console.WriteLine(Singleton3.Instance);
                //});
    
                //Task.Run(() =>
                //{
                //    Console.WriteLine(Singleton4.Instance);
                //});
                //Task.Run(() =>
                //{
                //    Console.WriteLine(Singleton4.Instance);
                //});
               
    
                Task.Run(() =>
                {
                    Console.WriteLine(Singleton5.Instance);
                });
                Task.Run(() =>
                {
                    Console.WriteLine(Singleton5.Instance);
                });
                Console.ReadKey();
    
            }
        }

    这里,会在每次创建一种Singleton模式的实现方法之后,在这里调用。

    里面会用两个线程来模拟多线程的情况。

    而在单例的实现中,会在创建构造函数时,输出语句,来区别是否创建了多个对象。

    效果如下示例:

    构造函数只调用了一次。

    方法一

    单线程情况下的一般实现。

    代码如下:

    Singleton1.cs

     1     public sealed class Singleton1
     2     {
     3         //私有的构造函数
     4         private Singleton1()
     5         {
     6             //Console.WriteLine($"Singleton1生成了...{Guid.NewGuid()}");
     7         }
     8         private static Singleton1 instance = null;
     9         /// <summary>
    10         /// 在静态属性Instance中,只有在instance为null时,才创建一个实例以避免重复
    11         /// </summary>
    12         public static Singleton1 Instance
    13         {
    14             get
    15             {
    16                 //如果instance为空,则创建实例
    17                 if (instance == null)
    18                 {
    19                     //Thread.Sleep(3000);  //模拟多线程同时到达这里
    20                     instance = new Singleton1();
    21                 }
    22                 return instance;
    23             }
    24         }
    25     }

    在上述代码中,Singleton1的静态属性Instance中,只有在instance为null的时候才创建一个实例以避免重复创建。

    同时我们把构造函数定义为私有函数,这个就可以确保只创建一个实例。

    但是这种方法只适合单线程,多线程情况下,就有问题了。

    方法二

    为了保证多线程环境下,我们还是只能得到一个类型的实例,需要加上一个同步锁。

    代码如下:

    Singleton2.cs

     1     public sealed class Singleton2
     2     {
     3         /// <summary>
     4         /// 私有的构造函数
     5         /// </summary>
     6         private Singleton2()
     7         {
     8             Console.WriteLine($"Singleton2生成了...{Guid.NewGuid()}");
     9         }
    10         //用作同步锁
    11         private static readonly object syncObj = new object();
    12 
    13         private static Singleton2 instance = null;
    14 
    15 
    16         public static Singleton2 Instance
    17         {
    18             get
    19             {
    20                 //添加同步锁
    21                 lock (syncObj)
    22                 {
    23                     //如果instance为null,则新建
    24                     if (instance == null)
    25                     {
    26                         Thread.Sleep(3000);
    27                         instance = new Singleton2();
    28                     }
    29                       
    30                 }
    31                 return instance;
    32             }
    33         }
    34     }

    我们还是假设有两个线程同时想创建一个实例。由于在一个时刻只有一个线程能得到同步锁,

    当第一个线程加上锁时,第二个线程只能等待。当第一个线程创建出实例之后,第二个线程就不会重复创建实例了,

    这样就保证了我们在多线程环境中也只能得到一个实例。

    但是呢,这种方法也不完美。我们每次通过属性Instance得到Singleton2的实例,都会试图加上一个同步锁,

    而加锁时一个非常耗时的操作,在没有必要的时候我们应该尽量避免。

    方法三

    我们可以这样:加同步锁前后两次判断实例是否已经存在

    我们只是在实例还没有创建之前需要加锁操作,以保证只有一个线程创建出实例。而当实例已经创建之后,我们已经不需要再做加锁操作了。

    改进代码如下:

    Singleton3.cs

     1     public sealed class Singleton3
     2     {
     3         //私有构造函数
     4         private Singleton3()
     5         {
     6             Console.WriteLine($"Singleton3生成了...{Guid.NewGuid()}");
     7         }
     8         //创建同步锁对象
     9         private static readonly object syncObj = new object();
    10 
    11         private static Singleton3 instance = null;
    12 
    13         public static Singleton3 Instance
    14         {
    15             get
    16             {
    17                 //instance为null,这里可能有两个线程同时到达,即都判断为null的情况
    18                 if (instance == null)
    19                 {
    20                     Thread.Sleep(3000); //会同时有两个线程等在这里
    21                     //加上同步锁,即只放一个线程过去
    22                     lock (syncObj)
    23                     {
    24                         //如果此时instance还未null,则新建instance;否则,跳过
    25                         if(instance==null)
    26                             instance = new Singleton3();
    27                     }
    28                 }
    29                 return instance;
    30             }
    31         }
    32     }

    这样的代码实现起来比较复杂,容易出错,我们还有更优秀的解法。

    方法四

    C# 语法中有一个函数能够保证只调用一次,那就是静态构造函数。

    代码如下:

    Singleton4.cs

     1      public sealed class Singleton4
     2     {
     3         static Singleton4() //静态构造函数
     4         {
     5             Console.WriteLine($"Singleton4生成了...{Guid.NewGuid()}");
     6         }
     7 
     8         private static Singleton4 instance = new Singleton4();
     9         
    10         public static Singleton4 Instance
    11         {
    12             get
    13             {
    14                 Thread.Sleep(3000);
    15                 return instance;
    16             }
    17         }
    18     }

     由于C# 是在调用静态构造函数时初始化静态变量,.NET运行时能够确保只调用一次静态构造函数,这样我们就能够保证只初始化一次instance。

    C#中调用静态构造函数的时机不是由程序员掌控的,而是当.NET运行时,发现第一次使用一个类型的时候自动调用该类型的静态构造函数。

    方法五

    这种方法实现的Singleton,可以很好的解决 方法四 中Singleton4的实例创建时机过早的问题:

     1     public sealed class Singleton5
     2     {
     3         Singleton5()
     4         {
     5             Console.WriteLine($"Singleton5生成了...{Guid.NewGuid()}");
     6         }
     7 
     8         public static Singleton5 Instance
     9         {
    10             get
    11             {
    12                 return Nested.instance;
    13             }
    14         }
    15 
    16         class Nested
    17         {
    18             static Nested()
    19             {
    20 
    21             }
    22             internal static readonly Singleton5 instance = new Singleton5();
    23         }
    24     }

    在这段代码中,我们在内部定义了一个私有类型的 Nested。

    当我们第一次用到这个嵌套类型的时候,会调用静态构造函数创建Singleton5的实例 instance。

    类型Nested只在属性Singleton5.Instance中被用到,由于其私有属性,他人无法使用Nested类型。

    因此,当我们第一次试图通过属性Singleton5.Instance得到Singleton5的实例时,会自动调用Nested的静态构造函数创建实例 instance

    总结

    推荐解法,方法四,或者方法五

    其中方法四利用了C#的静态构造函数的特性,确保只创建一个实例。

    第五种方法利用了私有嵌套类型的特性,做到只在需要的时候才会创建实例,提高空间使用率。

  • 相关阅读:
    springmvc乱码问题
    51nod 还是01串
    51nod 1276 岛屿的数量
    poj 2486 a apple tree
    hdu 1011 Starship Troopers
    poj 1155 TELE
    hdu 4586 Play the Dice
    hdu 5023 A Corrupt Mayor's Performance Art(线段树水题)
    Appleman and Tree
    hdu 4003
  • 原文地址:https://www.cnblogs.com/Vincent-yuan/p/11361369.html
Copyright © 2020-2023  润新知