【面试题2】实现Singleton模式
【题目描述】
设计一个类,我们只能生成该类的一个实例。
【解决方案】
1. 只适用于单线程环境
单线程环境下可以正常工作。
但是在多线程环境下,如果两个线程同时判断到instance为null,那么这两个线程都会创建一个实例,此时的Singleton就不满足单例模式的要求了。
1 public sealed class Singleton 2 { 3 private Singleton() 4 { 5 } 6 7 private static Singleton instance = null; 8 public static Singleton Instance 9 { 10 get 11 { 12 if (instance == null) 13 instance = new Singleton(); 14 return instance; 15 } 16 } 17 }
2. 多线程环境能工作,但效率不高
加同步锁处理后,能保证在多线程环境中也只创建一个实例。
但是由于在调用属性Instance时都会加同步锁,加锁又是一个非常耗时的操作,在没有必要的时候我们应该尽量避免。
1 public sealed class Singleton 2 { 3 private Singleton() 4 { 5 } 6 7 private static readonly Object syncObj = new object(); 8 9 private static Singleton instance = null; 10 public static Singleton Instance 11 { 12 get 13 { 14 lock (syncObj) 15 { 16 if (instance == null) 17 instance = new Singleton(); 18 } 19 return instance; 20 } 21 } 22 }
3. 加同步锁前后两次判断实例是否已经存在
加锁之前进行判断实例是否已经创建,可以保证实例已经创建后,则可以直接返回instance,而无需去进行加锁操作,保证只创建一个实例,相比上述方法,也提高了效率。
由于此代码实现比较繁杂且容易出错,我们可以尝试更好的解决方案。
1 public sealed class Singleton 2 { 3 private Singleton() 4 { 5 } 6 7 private static readonly Object syncObj = new object(); 8 9 private static Singleton instance = null; 10 public static Singleton Instance 11 { 12 get 13 { 14 if (instance == null) 15 { 16 lock (syncObj) 17 { 18 if (instance == null) 19 instance = new Singleton(); 20 } 21 } 22 return instance; 23 } 24 } 25 }
4. 利用静态构造函数
由于C#是在调用静态构造函数时初始化静态变量,.NET运行时能够确保只调用一次静态构造函数,这样我们就可以确保只初始化一次instance。
但是,根据静态构造函数的特性,实例instance并不是第一次调用属性Singleton.Instance时创建,而是在第一次用到Singleton的时候就会被创建。假设我们在Singleton中添加一个静态方法调用该静态方法是不需要创建实例的,但是由于你使用了Singleton,他会自动调用静态构造函数并创建实例,造成过早地创建实例,从而降低内存的使用效率。
1 public sealed class Singleton 2 { 3 private Singleton() 4 { 5 } 6 7 private static Singleton instance = new Singleton(); 8 public static Singleton Instance 9 { 10 get 11 { 12 return instance; 13 } 14 } 15 }
5. 实现按需创建实例
内部定义一个私有类型Nested,类型Nested只在调用Singeton.Instance时被使用,由于其私有属性他人无法使用Nested类型。
因此当我们第一次调用Sington.Instance时,会调用Nested的静态构造函数,并初始化静态变量。如果我们不调用属性Sington.Instance,则不会创建instance实例,这样就做到了按需创建。
1 public sealed class Singleton 2 { 3 Singleton() 4 { 5 } 6 7 public static Singleton Instance 8 { 9 get 10 { 11 return Nested.instance; 12 } 13 } 14 15 class Nested 16 { 17 static Nested() 18 { 19 } 20 internal static readonly Singleton instance = new Singleton(); 21 } 22 }
【本题扩展】
五种单例模式的实现把类型标记为sealed,表示他们不能作为其他类型的基类。现在我们要定义一个表示总统的类型President,可以从该类型继承出FrenchPresident和AmericanPresident等类型,这些派生类都只能产生一个实例。请问该如何设计实现这些类型?
我的代码实现,仅供参考:
1 class President 2 { 3 } 4 5 sealed class FrenchPresident:President 6 { 7 FrenchPresident() 8 { 9 } 10 11 public static FrenchPresident Instance 12 { 13 get 14 { 15 return Nested.instance; 16 } 17 } 18 19 class Nested 20 { 21 static Nested() 22 { 23 } 24 internal static readonly FrenchPresident instance = new FrenchPresident(); 25 } 26 } 27 28 sealed class AmericanPresident : President 29 { 30 AmericanPresident() 31 { 32 } 33 34 public static AmericanPresident Instance 35 { 36 get 37 { 38 return Nested.instance; 39 } 40 } 41 42 class Nested 43 { 44 static Nested() 45 { 46 } 47 internal static readonly AmericanPresident instance = new AmericanPresident(); 48 } 49 }