剑指Offer面试题:1.实现Singleton模式
说来惭愧,自己在毕业之前就该好好看看《剑指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#的静态构造函数的特性,确保只创建一个实例。第五种方法利用私有嵌套类型的特性,做到只在真正需要的时候才会创建实例,提高空间使用效率。
剑指Offer面试题:1.实现Singleton模式的更多相关文章
- 剑指Offer:面试题15——链表中倒数第k个结点(java实现)
问题描述 输入一个链表,输出该链表中倒数第k个结点.(尾结点是倒数第一个) 结点定义如下: public class ListNode { int val; ListNode next = null; ...
- 剑指offer面试题3 二维数组中的查找(c)
剑指offer面试题三:
- 剑指Offer——笔试题+知识点总结
剑指Offer--笔试题+知识点总结 情景回顾 时间:2016.9.23 12:00-14:00 19:00-21:00 地点:山东省网络环境智能计算技术重点实验室 事件:笔试 注意事项:要有大局观, ...
- C++版 - 剑指offer之面试题37:两个链表的第一个公共结点[LeetCode 160] 解题报告
剑指offer之面试题37 两个链表的第一个公共结点 提交网址: http://www.nowcoder.com/practice/6ab1d9a29e88450685099d45c9e31e46?t ...
- C++版 - 剑指offer 面试题23:从上往下打印二叉树(二叉树的层次遍历BFS) 题解
剑指offer 面试题23:从上往下打印二叉树 参与人数:4853 时间限制:1秒 空间限制:32768K 提交网址: http://www.nowcoder.com/practice/7fe2 ...
- C++版 - 剑指offer 面试题39:判断平衡二叉树(LeetCode 110. Balanced Binary Tree) 题解
剑指offer 面试题39:判断平衡二叉树 提交网址: http://www.nowcoder.com/practice/8b3b95850edb4115918ecebdf1b4d222?tpId= ...
- Leetcode - 剑指offer 面试题29:数组中出现次数超过一半的数字及其变形(腾讯2015秋招 编程题4)
剑指offer 面试题29:数组中出现次数超过一半的数字 提交网址: http://www.nowcoder.com/practice/e8a1b01a2df14cb2b228b30ee6a92163 ...
- C++版 - 剑指Offer 面试题39:二叉树的深度(高度)(二叉树深度优先遍历dfs的应用) 题解
剑指Offer 面试题39:二叉树的深度(高度) 题目:输入一棵二叉树的根结点,求该树的深度.从根结点到叶结点依次经过的结点(含根.叶结点)形成树的一条路径,最长路径的长度为树的深度.例如:输入二叉树 ...
- C++版 - 剑指offer 面试题24:二叉搜索树BST的后序遍历序列(的判断) 题解
剑指offer 面试题24:二叉搜索树的后序遍历序列(的判断) 题目:输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果.如果是则返回true.否则返回false.假设输入的数组的任意两个 ...
- C++版 - 剑指Offer 面试题45:圆圈中最后剩下的数字(约瑟夫环问题,ZOJ 1088:System Overload类似)题解
剑指Offer 面试题45:圆圈中最后剩下的数字(约瑟夫环问题) 原书题目:0, 1, - , n-1 这n个数字排成一个圈圈,从数字0开始每次从圆圏里删除第m个数字.求出这个圈圈里剩下的最后一个数字 ...
随机推荐
- Post with HttpClient
HttpClient是Java中经常使用的Http Client,总结下HttpClient4中经常使用的post请求用法. 1 Basic Post 使用2个参数进行post请求: @Test pu ...
- lodash
lodash常用函数一 <!DOCTYPE html> <html lang="en"> <head> <meta charset=&qu ...
- maven权威指南学习笔记(一)——简介
maven是什么?有什么用? Maven是一个项目管理工具,它包含了 一个项目对象模型 (Project Object Model), 一组标准集合, 一个项目生命周期(Pro ...
- 使用Javascript快速获取URL参数
首先:原文在这 Quick Tip: Get URL Parameters with JavaScript function getAllUrlParams(url) { var queryStr ...
- Centos 6.5 X64 环境下编译 hadoop 2.6.0 --已验证
Centos 6.5 x64 hadoop 2.6.0 jdk 1.7 protobuf-2.5.0 maven-3.0.5 set environment export JAVA_HOME=/hom ...
- jq 剪切板
文章链接 http://www.cnblogs.com/lkxsnow/p/5372665.html http://www.w3cfuns.com/notes/17735/020c2e68a60342 ...
- android二维码生成
前生: 一维码:条形码 数字 缺点:不好看,占面积, 好了,请看效果图: 在准备之前我们要导一个包:core-3.2.1.jar 下载请访问: http://download.csdn.net/do ...
- Good Bye 2016
A - New Year and Hurry (water) #include <bits/stdc++.h> using namespace std; int main() { ]; ; ...
- PDA手持扫描资产标签,盘点完成后将数据上传到PC端,固定资产系统查看盘点结果
固定资产管理系统介绍: 致力于研发条码技术.集成条码系统的专业性公司,针对客户的不同需求,提供一站式的企业条码系统解决方案:包括功能强大的软件系统.安全可靠的无线网络.坚固耐用的硬件系统.灵活易用的管 ...
- [翻译svg教程]Path元素 svg中最神奇的元素!
先看一个实例 <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999 ...