来源:剑指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. Memcache内存缓存框架

    转载请注明原文地址:https://www.cnblogs.com/ygj0930/p/10923221.html 一:Memcache是什么,为什么要用它 MemCache是一个高性能.“分布式”的 ...

  2. ansible自动化运维04

    ansible playbook ansible-playbook命令格式: ansible-playbook  [option]  filename(剧本名字) ansible-playbook 命 ...

  3. 设置linux代理完成apt-get

    最近ubuntu的服务器被公司关闭了外网使用权限,但是安装软件又需要连接外网,那么就只能通过代理来连接了. 先按照下面的这篇帖子来设置windows端的代理. https://blog.csdn.ne ...

  4. adb 命令之push pull

    C:\Users\ceshi>adb pull /storage/emulated/legacy/00001.vcf D:/E:\eclipse\Demo1>adb push E:\ecl ...

  5. 架构篇 | LAMP 架构应用案例 - 部署 PHPMyAdmin 系统(二)

    作者 | JackTian 微信公众号 | 杰哥的IT之旅(ID:Jake_Internet) 转载请联系授权(微信ID:Hc220066)备注:来自博客园 1.什么是 phpMyadmin 系统? ...

  6. STM32片上Flash容量大小命名规则

  7. NOIP 2013 积木大赛

    洛谷 P1969 积木大赛 洛谷传送门 JDOJ 2229: [NOIP2013]积木大赛 D2 T1 JDOJ传送门 题目描述 春春幼儿园举办了一年一度的"积木大赛".今年比赛的 ...

  8. 历届试题 危险系数-(dfs+记录路径)

     历届试题 危险系数   问题描述 抗日战争时期,冀中平原的地道战曾发挥重要作用. 地道的多个站点间有通道连接,形成了庞大的网络.但也有隐患,当敌人发现了某个站点后,其它站点间可能因此会失去联系. 我 ...

  9. java 的守护进程脚本

    #!/bin/sh ] do Tag=`ps -ef|grep 'jar包名称'|grep -v grep|wc -l|awk '{printf $1"\n"}'` ] then ...

  10. 字符串的方法slice、substr、substring对比

    三个方法的参数1都代表子串开始位置,参数2在slice和substring中表示结束位置,而在substr中代表的则是子串长度: 对于负数态度,当出现在参数1的位置时,slice和substr从末尾开 ...