C#实现单例模式的6种方法小结
作者:五维思考 发布时间:2023-09-13 21:02:57
介绍
单例模式是软件工程学中最富盛名的设计模式之一。从本质上看,单例模式只允许被其自身实例化一次,且向外部提供了一个访问该实例的接口。通常来说,单例对象进行实例化时一般不带参数,因为如果不同的实例化请求传递的参数不同的话会导致问题的产生。(若多个请求都是传递的同样的参数的话,工厂模式更应该被考虑)
C#中实现单例有很多种方法,本文将按顺序介绍非线程安全、完全懒汉式、线程安全和低/高性能集中版本。
在所有的实现版本中,都有以下几个共同点:
唯一的、私有的且无参的构造函数,这样不允许外部类进行实例化;
类是密封的,尽管这不是强制的,但是严格来讲从上一点来看密封类能有助于JIT的优化;
一个静态变量应该指向类的唯一实例;
一个公共的静态变量用于获得这个类的唯一实例(如果需要,应该创建它);
需要注意的是,本文中所有的例子中都是用一个 public static Instance的变量来访问单例类实例,要将其转换成公共函数是很容易的,但是这样并不会带来效率和线程安全上的提升。
Version 1 - 非线程安全
public sealed class Singleton
{
private static Singleton instance = null;
private Singleton() { }
public static Singleton Instance
{
get
{
if (instance == null)
{
instance = new Singleton();
}
return instance;
}
}
}
该版本在多线程下是不安全的,会创建多个实例,请不要在生产环境中使用!
因为如果两个线程同时运行到if(instance==null)判断时,就会创建两个实例,这是违背单例模式的初衷的。实际上在后面那个线程进行判断是已经生成了一个实例,但是对于不同的线程来说除非进行了线程间的通信,否则它是不知道的。
Version 2 - 简单的线程安全
public sealed class Singleton2
{
private static Singleton2 instance = null;
private static readonly object obj = new object();
private Singleton2() { }
public Singleton2 Instance
{
get
{
lock (obj)
{
if (instance == null)
{
instance = new Singleton2();
}
return instance;
}
}
}
}
该版本是线程安全的。通过对一个过线程共享的对象进行加锁操作,保证了在同一时刻只有一个线程在执行lock{}里的代码。当第一个线程在进行instance判断或创建时,后续线程必须等待直到前一线程执行完毕,因此保证了只有第一个线程能够创建instance实例。
但不幸的是,因为每次对instance的请求都会进行lock操作,其性能是不佳的。
需要注意的是,这里使用了一个private static object变量进行锁定,这是因为当如果对一个外部类可以访问的对象进行锁定时会导致性能低下甚至死锁。因此通常来说为了保证线程安全,进行加锁的对象应该是private的。
Version 3 - Double-check locking的线程安全
public sealed class Singleton3
{
private static Singleton3 instance = null;
private static object obj = new object();
private Singleton3() { }
public static Singleton3 Instance
{
get
{
if (instance == null)
{
lock (obj)
{
if (instance == null)
{
instance = new Singleton3();
}
}
}
return instance;
}
}
}
该版本中试图去避免每次访问都进行加锁操作并实现线程安全。然后,这段代码对Java不起作用,因Java的内存模型不能保证在构造函数一定在其他对象引用instance之前完成。还有重要的一点,它不如后面的实现方式。
Version 4 - 不完全懒汉式,但不加锁的线程安全
public sealed class Singleton4
{
private static readonly Singleton4 instance = new Singleton4();
/// <summary>
/// 显式的静态构造函数用来告诉C#编译器在其内容实例化之前不要标记其类型
/// </summary>
static Singleton4() { }
private Singleton4() { }
public static Singleton4 Instance { get { return instance; } }
}
这个版本是的实现非常的简单,但是却又是线程安全的。C#的静态构造函数只有在当其类的实例被创建或者有静态成员被引用时执行,在整个应用程序域中只会被执行一次。使用当前方式明显比前面版本中进行额外的判断要快。
当然这个版本也存在一些瑕疵:
不是真正意义上的懒汉模式(需要的时候才创建实例),若单例类还存在其他静态成员,当其他类第一次引用这些成员时便会创建该instance。下个版本实现会修正这个问题;
只有.NET中才具有beforefieldinit特性,即懒汉式实现。且在.Net 1.1以前的编译器不支持,不过这个现在来看问题不大;
所有版本中,只有这里将instance设置成了readonly,这不仅保证了代码的高校且显得十分短小。
Version 5 - 完全懒汉实例化
public sealed class Singleton5
{
private Singleton5() { }
public static Singleton5 Instance { get { return Nested.instance; } }
private class Nested
{
static Nested() { }
internal static readonly Singleton5 instance = new Singleton5();
}
}
该版本看起来稍微复杂难懂,其实只是在写法上实现了上一版本的瑕疵,通过内嵌类的方式先实现了只有在真正应用Instance时才进行实例化。其性能表现与上一版本无异。
Version 6 - 使用.NET 4 Lazy type 特性
public sealed class Singleton6
{
private static readonly Lazy<Singleton6> lazy =
new Lazy<Singleton6>(()=> new Singleton6());
public static Singleton6 Instance { get { return lazy.Value; } }
private Singleton6() { }
}
如果你使用的是.NET 4或其以上版本,可以使用System.Lazy type来实现完全懒汉式。其代码看起来也很简洁且性能表现也很好。
性能 VS 懒汉式
一般情况下,我们并不需要实现完全懒汉式,除非你的构造初始化执行了某些费时的工作。因此一般的,我们使用显式的静态构造函数就能够适用。
本文翻译自Implementing the Singleton Pattern in C#
Exception
有时候在进行构造函数初始化时可能 会抛出异常,但这对整个应用程序来说不应该是致命的,所以可能的情况下,你应该自己处理这种异常情况。
来源:https://www.cnblogs.com/zhaoshujie/p/14323654.html


猜你喜欢
- 在C#中,数组由于是固定长度的,所以常常不能满足我们开发的需求。由于这种限制不方便,所以出现了ArrayList。ArrayList、Lis
- b/s系统中对http请求数据的校验多数在客户端进行,这也是出于简单及用户体验性上考虑,但是在一些安全性要求高的系统中服务端校验是不可缺少的
- 1、修改全局配置文件(application.yml)server: port: 9001 servlet: &nb
- GitHub 地址:quickjs-android-wrapper特性支持 Java 和 JavaScript 类型互转支持 Promise
- 先看一下Android悬浮按钮点击回到顶部的效果:FloatingActionButton是Design Support库中提供的一个控件,
- Android中获取资源 id 及资源 id 的动态获取我们平时获取资源是通过 findViewById 方法进行的,比如我们常
- 开始学习WebSocket,准备用它来实现一个在页面实时输出log4j的日志以及控制台的日志。首先知道一些基础信息:1.java7 开始支持
- 一、ArrayList类概述什么是集合:提供一种存储空间可变的存储模型,存储的数据容量可以发生改变ArrayList集合的特点:底层是数组实
- 本文实例为大家分享了java实现图片角度旋转并获得图片信息的具体代码,供大家参考,具体内容如下public class Demo {/**
- 最近因考虑接口安全问题,有实现给WEB API实现统一的参数鉴权功能,以防止请求参数被篡改或重复执行,参数鉴权方法基本与常见的鉴权思路相同,
- 首先当我们将Dwr3配置好以后,我们可以在浏览器中测试一下,查看一下我们配置的Dwr有没有生效,方法是http://localhost:[你
- 一般数据库的编码是utf8,utf8是不支持存储表情符的,当存入的微信昵称带有表情符时就会出现乱码情况,有两种解决方法:1.mysql数据库
- 一 模式介绍重试模式,是应用在异常处理中,发生异常的时候,能够对业务程序进行重新调用,在实际中,可以使用Polly提供稳定,简单的用法,自己
- 数组的定义数组本质上就是让我们能 " 批量 " 创建相同类型的变量。数组的三种语法格式1、 数据类型 [] 数组名称 =
- @ApiImplicitParam作用在方法上,表示单独的请求参数参数name:参数名。value:参数的具体意义,作用。required:
- 常用的字符串转date,和日期转字符串的方法,具体内容如下package com.cq2022.zago.base.util; import
- 本文实例为大家分享了Android实现闪屏页效果的具体代码,供大家参考,具体内容如下1.效果图2.闪屏页逻辑及布局2.1 activity_
- 本文章向大家介绍JAVA爬取天天基金网数据,主要包括JAVA爬取天天基金网数据使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参
- 需求:字符串(字符串只有一位小数)转float进行运算, 将结果转成字符串(保留一位小数)直接上代码:float f1 = 0.1f;Str
- 使用范围: 只能作用在方法和构造函数之上@SneakyThrows注解的作用得从java的异常设计体系说起。java中常见的异常有两种:Ex