剑指Offer学习总结-实现Singleton模式
本系列为剑指Offer学习总结,主要是代码案例的分析和实现:
书籍链接:http://product.dangdang.com/24242724.html
原作者博客:http://zhedahht.blog.163.com/blog/static/254111742011101624433132/
实现Singleton模式
题目
题目: 设计一个类, 我们只能生成该类的一个实例。
只能生成一个实例的类是实现了 Singleton (单例) 模式的类型,
单例设计模式在面向对象程序设计中起着举足较重的作用。
不好的解法一:只适用于单线程环境
单例实现的基本思路:
私有化构造函数(禁止他人创建实例)
定义一个私有的静态(类名直接访问)变量用于存储单例类对象
提供给外界访问这个对象的方法(或者属性)
public sealed class Singleton1
{
//私有化构造函数
private Singleton1()
{
}
//私有静态类对象实例
private static Singleton1 instance = null;
//外界访问的属性
public static Singleton1 Instance
{
get
{
if (instance == null)
instance = new Singleton1();
return instance;
}
}
}
不好的解法二:多单线程可工作,但效率不高
解法一中的代码在单线程的时候工作正常, 但在多线程的情况下就有问题了.
设想如果两个线程同时运行到 if (instance == null),并且 instance 的确没有创建时,
那么两个线程都会创建一个实例, 此时类型Singleton1 就不再满足单例模式的要求了 ,
为了保证在多线程环境下我们还是只能得到类型的一个实例, 需要加上一个同步锁。
public sealed class Singleton2
{
private Singleton2()
{
}
//线程锁
private static readonly object syncObj = new object();
private static Singleton2 instance = null;
public static Singleton2 Instance
{
get
{
//加锁
lock (syncObj)
{
if (instance == null)
instance = new Singleton2();
}
return instance;
}
}
}
我们还是假设有两个线程同时想创建一个实例。 由于在一个时刻只有一个线程能得到同步锁,
当第一个线程加上锁时, 第二个线程只能等待。当第一个线程发现实例还没有创建时, 它创建出一个实例。 接着第一个线程释放同步锁, 此时第二个线程可以加上同步锁, 并运行接下来的代码。
这个时候由于实例己经被第一个线程创建出来了, 第二个线程就不会重复创建实例了, 这样就保证了我们在多线程环境中也只能得到一个实例。
但是解法二还不是很完美, 我们每次通过属性 Instance 得到SingUton2 的实例, 都会试图加上一个同歩锁, 而加锁是–个非常耗时的操作, 在没有必要的时候我们应该尽量避免。
可行的解法三:加同步锁前后两次判断实例是否已存在
public sealed class Singleton3
{
private Singleton3()
{
}
private static object syncObj = new object();
private static Singleton3 instance = null;
public static Singleton3 Instance
{
get
{
//加锁前判断是否为空
if (instance == null)
{
lock (syncObj)
{
if (instance == null)
instance = new Singleton3();
}
}
return instance;
}
}
}
Singleton3 中只有当 instance 为 null 即没有创建时, 需要加锁操作 a 当
instance 已经创建出来之后, 则无须加锁。 因为只在第一次的时候 instance
为 null, 因此只在第一次试图创建实例的时候需要加锁
Singleton3 用加锁机制来确保在多线程环境下只创建一个实例, 并用
两个 if 判断来提高效率。 这样的代码实现起来比较复杂, 容易出错, 我们
还有更加优秀的解法
强烈推荐的解法一:利用静态构造函数
C#的语法中有一个函数能够确保只调用一次, 那就是静态构造函数
public sealed class Singleton4
{
//静态构造函数 原文书籍这里是private,我个人认为原书这里是错误的
static Singleton4()
{
}
public static void Print()
{
Console.WriteLine("Singleton4 Print");
}
//创建的时候直接实例化
private static Singleton4 instance = new Singleton4();
//属性直接返回即可
public static Singleton4 Instance
{
get
{
return instance;
}
}
}
C#中调用静态构造函数的时机不是由程序员掌控的, 而是当.NET 运行时发现第一次使用一个类型的时候
自动调用该类型的静态构造函数。
因此在 Singleton4 中, 实例 instance 并不是第一次调用属性 Singleton4,Instance的时候创建,
而是在第一次用到 Singlet(m4 的时候就会被创建。
假设我们在Singleton4 中添加一个静态方法,调用该静态方法是不需要创建一个实例的,
Singleton4 方式实现的单例模式,则仍然会过早地创建实例,从时降低内存的使用效率。
强烈推荐的解法一:实现按需创建实例
public sealed class Singleton5
{
Singleton5()
{
}
public static void Print()
{
Console.WriteLine("Singleton5 Print");
}
public static Singleton5 Instance
{
get
{
return Nested.instance;
}
}
class Nested
{
static Nested()
{
}
internal static readonly Singleton5 instance = new Singleton5();
}
}
我们在内部定义了一个私有类型 Nested,当第一次用到这个嵌套类型的时候,
会调用静态构造函数创建 Singleton5的实例 instance。
类型 Nested 只在属性 SingietonS.Instance 中被用到,
由于其私有属性他人无法使用 Nested 类 ,因 此 当 我 们 第 一 次 试 图 通 过 属 性
Singleton5.Instance 得到 Singieton5的实例时, 会自动调用 Nested 的静态构造函数
创建实例Instance。 如果我们不调用属性 Singleton5.Instance
那么就不会触发.NET 运行时调用 Nested, 也不会创建实例, 这样就真正做到了按需创建
关于更多的设计模式方面,可以查看我的设计模式系列的博客总结。
http://blog.csdn.net/wwlcsdn000/article/details/78678975