说来惭愧,自己在毕业之前就该好好看看《剑指Offer》这本书的,但是各种原因就是没看,也因此错过了很多机会,后悔莫及。但是后悔是没用的,现在趁还有余力,把这本书好好看一遍,并通过C#通通实现一遍,并记录在我的博客中,作为学习笔记。

一、题目:实现Singleton模式

题目:设计一个类,我们只能生成该类的一个实例。 

  只能生成一个实例的类是实现了Singleton(单例)模式的类型。由于设计模式在面向对象程序设计中起着举足轻重的作用,在面试过程中很多公司都喜欢问一些与设计模式相关的问题。在常用的模式中,Singleton是唯一一个能够用短短几十行代码完整实现的模式。因此,写一个Singleton的类型是一个很常见的面试题。

  例如,在一个Flappy Bird游戏中,小鸟这个游戏对象在整个游戏中应该只存在一个实例,所有对于这个小鸟的操作(向上飞、向下掉等)都应该只会针对唯一的一个实例进行。

二、几种不好的解法

2.1 不好的解法一:只适用于单线程环境

    public sealed class Singleton1
{
private Singleton1() { } private static Singleton1 instance = null; public static Singleton1 Instance
{
get
{
if(instance == null)
{
instance = new Singleton1();
} return instance;
}
}
}

  解法一的代码在单线程的时候工作正常,但在多线程的情况下多个线程都会创建一个自己的实例,无法保证单例模式的要求。

2.2 不好的解法二:虽然在多线程环境中能工作但效率不高 

    public sealed class Singleton2
{
private Singleton2() { } private static readonly object syncObject = new object(); private static Singleton2 instance = null; public static Singleton2 Instance
{
get
{
// 每个线程来之前先等待锁
lock(syncObject)
{
if (instance == null)
{
instance = new Singleton2();
}
} return instance;
}
}
}

  解法二就保证了我们在多线程环境中也只能得到一个实例,但是加锁是一个非常耗时的操作,在没有必要的时候我们应该尽量避免

2.3 可行的解法三:加同步锁前后两次判断实例是否已存在

  前面讲到的线程安全的实现方式的问题是要进行同步操作,那么我们是否可以降低通过操作的次数呢?其实我们只需在同步操作之前,添加判断该实例是否为null就可以降低通过操作的次数了,这样是经典的Double-Checked Locking方法,修改上面的属性代码如下:

    public static Singleton3 Instance
{
get
{
// Double-Check 双重判断避免不必要的加锁
if (instance == null)
{
// 确定实例为空时再等待加锁
lock (syncObject)
{
// 确定加锁后实例仍然未创建
if (instance == null)
{
instance = new Singleton3();
}
}
} return instance;
}
}

  解法三用加锁机制来确保在多线程环境下只创建一个实例,并且用两个if判断来提高效率。但是,这样的代码实现起来比较复杂,容易出错。

三、两种较好的解法

3.1 较好的解法一:利用静态构造函数

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

    public sealed class Singleton4
{
private Singleton4() { }
// 在大多数情况下,静态初始化是在.NET中实现Singleton的首选方法。
static Singleton4() { } private static readonly Singleton4 instance = new Singleton4(); public static Singleton4 Instance
{
get
{
return instance;
}
}
}

  该解法是在 .NET 中实现 Singleton 的首选方法,但是,由于在C#中调用静态构造函数的时机不是由程序员掌控的,而是当.NET运行时发现第一次使用该类型的时候自动调用该类型的静态构造函数(也就是说在用到Singleton4时就会被创建,而不是用到Singleton4.Instance时),这样会过早地创建实例,从而降低内存的使用效率。此外,静态构造函数由 .NET Framework 负责执行初始化,我们对对实例化机制的控制权也相对较少。

3.2 较好的解法二:实现按需创建实例

    public sealed class Singleton5
{
private Singleton5() { } public static Singleton5 Instance
{
get
{
return Nested.instance;
}
} // 使用内部类+静态构造函数实现延迟初始化
class Nested
{
static Nested() { } internal static readonly Singleton5 instance = new Singleton5();
}
}

  该解法在内部定义了一个私有类型Nested。当第一次用到这个嵌套类型的时候,会调用静态构造函数创建Singleton5的实例instance。如果我们不调用属性Singleton5.Instance,那么就不会触发.NET运行时(CLR)调用Nested,也就不会创建实例,因此也就保证了按需创建实例(或延迟初始化)。

四、总结

  在前面的5种实现单例模式的方法中:

  第一种方法在多线程环境中不能正常工作,第二种模式虽然能在多线程环境中正常工作但时间效率很低,都不是面试官期待的解法。在第三种方法中我们通过两次判断一次加锁确保在多线程环境能高效率地工作。

  第四种方法利用C#的静态构造函数的特性,确保只创建一个实例。第五种方法利用私有嵌套类型的特性,做到只在真正需要的时候才会创建实例,提高空间使用效率。

作者:周旭龙

出处:http://edisonchou.cnblogs.com

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

剑指Offer面试题:1.实现Singleton模式的更多相关文章

  1. 剑指Offer:面试题15——链表中倒数第k个结点(java实现)

    问题描述 输入一个链表,输出该链表中倒数第k个结点.(尾结点是倒数第一个) 结点定义如下: public class ListNode { int val; ListNode next = null; ...

  2. 剑指offer面试题3 二维数组中的查找(c)

    剑指offer面试题三:

  3. 剑指Offer——笔试题+知识点总结

    剑指Offer--笔试题+知识点总结 情景回顾 时间:2016.9.23 12:00-14:00 19:00-21:00 地点:山东省网络环境智能计算技术重点实验室 事件:笔试 注意事项:要有大局观, ...

  4. C++版 - 剑指offer之面试题37:两个链表的第一个公共结点[LeetCode 160] 解题报告

    剑指offer之面试题37 两个链表的第一个公共结点 提交网址: http://www.nowcoder.com/practice/6ab1d9a29e88450685099d45c9e31e46?t ...

  5. C++版 - 剑指offer 面试题23:从上往下打印二叉树(二叉树的层次遍历BFS) 题解

    剑指offer  面试题23:从上往下打印二叉树 参与人数:4853  时间限制:1秒  空间限制:32768K 提交网址: http://www.nowcoder.com/practice/7fe2 ...

  6. C++版 - 剑指offer 面试题39:判断平衡二叉树(LeetCode 110. Balanced Binary Tree) 题解

    剑指offer 面试题39:判断平衡二叉树 提交网址:  http://www.nowcoder.com/practice/8b3b95850edb4115918ecebdf1b4d222?tpId= ...

  7. Leetcode - 剑指offer 面试题29:数组中出现次数超过一半的数字及其变形(腾讯2015秋招 编程题4)

    剑指offer 面试题29:数组中出现次数超过一半的数字 提交网址: http://www.nowcoder.com/practice/e8a1b01a2df14cb2b228b30ee6a92163 ...

  8. C++版 - 剑指Offer 面试题39:二叉树的深度(高度)(二叉树深度优先遍历dfs的应用) 题解

    剑指Offer 面试题39:二叉树的深度(高度) 题目:输入一棵二叉树的根结点,求该树的深度.从根结点到叶结点依次经过的结点(含根.叶结点)形成树的一条路径,最长路径的长度为树的深度.例如:输入二叉树 ...

  9. C++版 - 剑指offer 面试题24:二叉搜索树BST的后序遍历序列(的判断) 题解

    剑指offer 面试题24:二叉搜索树的后序遍历序列(的判断) 题目:输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果.如果是则返回true.否则返回false.假设输入的数组的任意两个 ...

  10. C++版 - 剑指Offer 面试题45:圆圈中最后剩下的数字(约瑟夫环问题,ZOJ 1088:System Overload类似)题解

    剑指Offer 面试题45:圆圈中最后剩下的数字(约瑟夫环问题) 原书题目:0, 1, - , n-1 这n个数字排成一个圈圈,从数字0开始每次从圆圏里删除第m个数字.求出这个圈圈里剩下的最后一个数字 ...

随机推荐

  1. 【Java EE 学习 53】【Spring学习第五天】【Spring整合Hibernate】【Spring整合Hibernate、Struts2】【问题:整合hibernate之后事务不能回滚】

    一.Spring整合Hibernate 1.如果一个DAO 类继承了HibernateDaoSupport,只需要在spring配置文件中注入SessionFactory就可以了:如果一个DAO类没有 ...

  2. CozyRSS开发记录8-解析一份RSS

    CozyRSS开发记录8-解析一份RSS 1.使用Rss20FeedFormatter解析RSS 使用Rss20FeedFormatter配合XmlReader来解析RSS非常的简单,几行搞定: 来试 ...

  3. 学习微信小程序之css5

    文本的装饰(下划线) 文本的位置,缩进

  4. TP5.0源生Excel导出

    PHPExcel类在TP5里边并不能很好的兼容,使用起来很麻烦. 不像是tp3.2那样直接import()加进来就能new,因为它里边的命名空间找不到.总是说undefined class. 如果是使 ...

  5. 解决scrollview和viewpager冲突

    import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; i ...

  6. [leetcode] 提醒整理之进制

    12. Integer to Roman Given an integer, convert it to a roman numeral. Input is guaranteed to be with ...

  7. MongoVUE1.6.9破解启动提示System.ArgumentException: 字体“Courier New”不支持样式“Regular”

    用MongoVUE,发现报错,报错信息如下: System.ArgumentException: 字体"Courier New"不支持样式"Regular". ...

  8. [转载] SSH入门学习基础教程

    在Linux系统中,OpenSSH是目前最流行的远程系统登录与文件传输应用,也是传统Telenet.FTP和R系列等网络应用的换代产品.其 中,ssh(Secure Shell)可以替代telnet. ...

  9. C#开发中常用方法3------Cookie的存取

    ---------------------------------------------------------------------------------------------------- ...

  10. nodejs复习04

    TCP/UDP网络应用 创建TCP服务器客户端 socket套接字对象实例,是对TCP协议的一个基本封装接口 clientt套接字对象实例 //server.js var net = require( ...