来源:剑指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. elastic date时区问题解决办法

    之前介绍filter date插件时就谈到时区问题,但是没有说明白.最近在使用range查询时间范围内的数据时出现了数据量不一致的情况.特地了解了下ELK Stack中关于时区的问题. 问题: 使用k ...

  2. Nginx 反向代理功能-实现Nginx tcp负载均衡

    Nginx 反向代理功能-实现Nginx tcp负载均衡 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任.

  3. 三、requests模块

    Requests: 让 HTTP 服务人类 虽然Python的标准库中 urllib2 模块已经包含了平常我们使用的大多数功能,但是它的 API 使用起来让人感觉不太好,而 Requests 自称 “ ...

  4. 【Hadoop】集群网络

  5. Git创建与简单使用

    一. 服务器端 创建空的仓库(以项目tm201为例) 1. git账户登录 2. 新建仓库目录 mkdir tm101.git && cd tm201.git 3. git初始化新的空 ...

  6. pycharm初识及格式化输出

    #_*_coding:utf-8_*_#作者:王佃元#日期:2019/12/6 #格式化输出name = input("Name")age = input("Age&qu ...

  7. ESA2GJK1DH1K升级篇: STM32远程乒乓升级,基于WIFI模块AT指令TCP透传方式,定时访问升级(含有数据校验)

    实现功能概要 定时使用http访问云端的程序版本,如果版本不一致,然后通过http下载最新的升级文件,实现升级. 测试准备工作(默认访问我的服务器,改为自己的服务器,请看后面说明) 一,下载BootL ...

  8. SDOI 2019 R2 摸鱼记

    其实并没有什么动力来写这篇游记,毕竟呢,明明已经做好了被吊打的心理准备,可是当 Day 2 挂到没分时,当看到自己在高一里还排名二十时,还是有些,有些难言的滋味.学长们该走的真的都走了,就要只剩下 z ...

  9. css规范思维导图(仅限于自己)

  10. tensorflow数据加载、模型训练及预测

    数据集 DNN 依赖于大量的数据.可以收集或生成数据,也可以使用可用的标准数据集.TensorFlow 支持三种主要的读取数据的方法,可以在不同的数据集中使用:本教程中用来训练建立模型的一些数据集介绍 ...