来源:剑指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. docker上启动mysql镜像,mysq中记录乱码解决方法

    在docker上启动一个mysql, 1. docker pull mysql 2. docker run --name mysql_dev -p 3306:3306 -e MYSQL_ROOT_PA ...

  2. 解决debugJDK源码看不到局部变量的值

    背景:使用的jdk1.8.0_201 问题描述:在eclispe中调试代码进入到JDK源码中,想看到某个变量的值得变化,发现此变量的值没法看到 解决方案: 1.进入到你安装本机的jdk目录下,找到sr ...

  3. Linux Tomcat安装及端口配置

    1.  JDK安装配置 待写 2. Tomcat安装配置 1,下载Tomcat链接,到启动测试. 将文件apache-tomcat-8.5.50.tar.gz移动到/usr/tomcat/下,并解压 ...

  4. Springboot环境搭建_第一个例子

    首先创建一个maven项目 maven项目创建完成之后,找到pom.xml配置节点.需要springboot-starter-web ,springboot-starter-test,springbo ...

  5. Nginx 核心配置-根目录root指令与别名alias指令实战案例

    Nginx 核心配置-根目录root指令与别名alias指令实战案例 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.试验环境说明 1>.虚拟机环境说明 [root@nod ...

  6. wpf学习资料链接(做记录)

    1.wpf快速入门系列:https://www.cnblogs.com/zhili/tag/WPF/

  7. USACO Party Invitations

    洛谷 P3068 [USACO13JAN]派对邀请函Party Invitations 洛谷传送门 JDOJ 2343: USACO 2013 Jan Silver 3.Party Invitatio ...

  8. ZJOI2019赛季回顾

    退役了. NOIP2018 day1没什么好说的. day2开考后看完题:这个T3 TM不是DDP吗? 考前刚学过这东西,还没去写过 当时不知道在想什么,胡了T1 60和T2 50分保底之后就去刚T3 ...

  9. 【java异常】expected at least 1 bean which qualifies as autowire candidate for this depende

    1.查看接口实现类是否加入注解,如service.repository等 2.查看spring配置文件是否自动扫描包    <context:component-scan base-packag ...

  10. git提交代码时出现was rejected by remote错误

    git常见问题 git是大家在公司基本都项目管理工具,有一次在改了一个bug提交远程提交就出现问题了. 解决方案 首先这个是远程提交的时候被项目权限拦截掉了,一般在我们都用配置ssh公钥的方式操作,那 ...