来源:剑指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

     public sealed class Singleton1
{
//私有的构造函数
private Singleton1()
{
//Console.WriteLine($"Singleton1生成了...{Guid.NewGuid()}");
}
private static Singleton1 instance = null;
/// <summary>
/// 在静态属性Instance中,只有在instance为null时,才创建一个实例以避免重复
/// </summary>
public static Singleton1 Instance
{
get
{
//如果instance为空,则创建实例
if (instance == null)
{
//Thread.Sleep(); //模拟多线程同时到达这里
instance = new Singleton1();
}
return instance;
}
}
}

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

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

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

方法二

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

代码如下:

Singleton2.cs

     public sealed class Singleton2
{
/// <summary>
/// 私有的构造函数
/// </summary>
private Singleton2()
{
Console.WriteLine($"Singleton2生成了...{Guid.NewGuid()}");
}
//用作同步锁
private static readonly object syncObj = new object(); private static Singleton2 instance = null; public static Singleton2 Instance
{
get
{
//添加同步锁
lock (syncObj)
{
//如果instance为null,则新建
if (instance == null)
{
Thread.Sleep();
instance = new Singleton2();
} }
return instance;
}
}
}

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

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

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

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

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

方法三

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

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

改进代码如下:

Singleton3.cs

     public sealed class Singleton3
{
//私有构造函数
private Singleton3()
{
Console.WriteLine($"Singleton3生成了...{Guid.NewGuid()}");
}
//创建同步锁对象
private static readonly object syncObj = new object(); private static Singleton3 instance = null; public static Singleton3 Instance
{
get
{
//instance为null,这里可能有两个线程同时到达,即都判断为null的情况
if (instance == null)
{
Thread.Sleep(); //会同时有两个线程等在这里
//加上同步锁,即只放一个线程过去
lock (syncObj)
{
//如果此时instance还未null,则新建instance;否则,跳过
if(instance==null)
instance = new Singleton3();
}
}
return instance;
}
}
}

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

方法四

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

代码如下:

Singleton4.cs

      public sealed class Singleton4
{
static Singleton4() //静态构造函数
{
Console.WriteLine($"Singleton4生成了...{Guid.NewGuid()}");
} private static Singleton4 instance = new Singleton4(); public static Singleton4 Instance
{
get
{
Thread.Sleep();
return instance;
}
}
}

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

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

方法五

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

     public sealed class Singleton5
{
Singleton5()
{
Console.WriteLine($"Singleton5生成了...{Guid.NewGuid()}");
} public static Singleton5 Instance
{
get
{
return Nested.instance;
}
} class Nested
{
static Nested()
{ }
internal static readonly Singleton5 instance = new Singleton5();
}
}

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

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

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

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

总结

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

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

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

剑指offer之面试题2:实现Singleton模式的更多相关文章

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

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

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

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

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

    剑指offer面试题三:

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

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

  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.假设输入的数组的任意两个 ...

随机推荐

  1. Windows 2016 & Windows 10 中IIS安装和配置PHP的步骤

    Windows 2016 和 Windows 10 内核是相同的,我们首先需要安装 Internet Information Services (IIS),当然 Win2016 跟 Win10 安装  ...

  2. grep基本详细使用

    过滤文件类容---grep grep正则表达式应用: #grep [option]"pattern" 文件名称 pattern模式 由普通字符和正则表达式的元字符组构成的条件 简单 ...

  3. Windows C盘扩容

    工具:大白菜pe1.将F盘数据拷贝放其他盘,并格式化F盘.2.进入大白菜PE,使用DiskGenius工具,右键C盘图示->扩容->C盘,继续其他操作.(其中,需将F盘释放为未使用,然后将 ...

  4. 五个goland进行go开发的小技巧

    五个goland进行go开发的小技巧 本文译自5 Tips To Speed Up Golang Development With IntelliJ Or Goland 确实很实用. 1. 实现int ...

  5. html-前端内容初识

    HTML解释: HTML是英文Hyper Text Mark-up Language(超文本标记语言)的缩写,他是一种制作万维网页面标准语言(标记).相当于定义统一的规则(W3C),大家都来遵守他,这 ...

  6. 【oracle】select into from 和 insert into select 的用法和区别

    select into from 和 insert into select都是用来复制表,两者的主要区别为: select into from 要求目标表不存在,因为在插入时会自动创建.insert ...

  7. 【myBatis】It's likely that neither a Result Type nor a Result Map was specified.

    因为mapper.xml里把resultType写成了parameterType

  8. Pandas | 23 分类数据

    通常实时的数据包括重复的文本列.例如:性别,国家和代码等特征总是重复的.这些是分类数据的例子. 分类变量只能采用有限的数量,而且通常是固定的数量.除了固定长度,分类数据可能有顺序,但不能执行数字操作. ...

  9. 2018-2019-2 20165313 《网络对抗技术》 Exp 9 Web安全基础

    一.实验要求 本实践的目标理解常用网络攻击技术的基本原理,做不少于7个题目,共3.5分.包括(SQL,XSS,CSRF).Webgoat实践下相关实验. 二.实验问题回答 (1)SQL注入攻击原理,如 ...

  10. 【Gamma】Scrum Meeting 10

    目录 写在前面 任务进度表 燃尽图 照片 写在前面 例会时间:6.8 22:30-23.00 例会地点:微信群语音通话 代码进度记录github在这里 任务进度表 注:点击链接跳转至相应的issue ...