【设计模式】单例模式 Singleton Pattern

通常我们在写程序的时候会碰到一个类只允许在整个系统中只存在一个实例(Instance)  的情况, 比如说我们想做一计数器,统计某些接口调用的次数,通常我们的数据库连接也是只期望有一个实例。Windows系统的系统任务管理器也是始终只有一个,如果你打开了windows管理器,你再想打开一个那么他还是同一个界面(同一个实例), 还有比如 做.Net平台的人都知道,AppDomain 对象,一个系统中也只有一个,所有的类库都会加载到AppDomain中去运行。只需要一个实例对象的场景,随处可见,那么有么有什么好的解决方法来应对呢? 有的,那就是 单例模式。

一、单例模式定义

单例模式(Singleton Pattern):确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。单例模式是一种对象创建型模式。

二、单例模式结构图

image

  • Singleton(单例):在单例类的内部实现只生成一个实例,同时它提供一个静态的GetInstance()工厂方法,让客户可以访问它的唯一实例;为了防止在外部对其实例化,将其构造函数设计为私有(private);在单例类内部定义了一个Singleton类型的静态对象,作为外部共享的唯一实例。

三、 单例模式典型代码

public class Singleton
{private static Singleton instance;private Singleton(){}public static Singleton GetInstance(){if(instance==null){instance=new Singleton();}return instance;}
}

客户端调用代码:

static void Main(string[] args)
{Singleton singleto = Singleton.GetInstance();
}

在C#中经常将统一访问点暴露出一个只读的属性供客户端程序使用,这样代码就变成了这样:

public class Singleton
{private static Singleton instance;private Singleton(){}public static Singleton GetInstance{get{if (instance == null){instance = new Singleton();}return instance;}}
}

客户端调用:

static void Main(string[] args)
{Singleton singleton = Singleton.GetInstance;
}

四、单例模式实例

1. 懒汉模式

假如我们要做一个程序计数器,一旦程序启动无论多少个客户端调用这个 计数器计数的结果始终都是在前一个的基础上加1,那么这个计数器类就可以设计成一个单例模式的类。

public class SingletonCounter
{private static SingletonCounter instance;private static int number=0;private SingletonCounter() { }public static SingletonCounter Instance{get{if (instance == null) instance = new SingletonCounter();number++;return instance;}}public int GetCounter(){return number;}
}

客户端调用:

static void Main(string[] args)
{//App A call the counter;SingletonCounter singletonA = SingletonCounter.Instance;int numberA = singletonA.GetCounter();Console.WriteLine("App A call the counter get number was:" + numberA);//App B call the counter;SingletonCounter singletonB = SingletonCounter.Instance;int numberB = singletonA.GetCounter();Console.WriteLine("App B call the counter get number was:" + numberB);Console.ReadKey();
}

输出结果:

image

这个实现是线程不安全的,如果有多个线程同时调用,并且又恰恰在计数器初始化的瞬间多个线程同时检测到了 instance==null为true情况,会怎样呢?这就是下面要讨论的 “加锁懒汉模式”

2、加锁懒汉模式

多个线程同时调用并且同时检测到 instance == null 为 true的情况,那后果就是会出现多个实例了,那么就无法保证唯一实例了,解决这个问题就是增加一个对象锁来确保在创建的过程中只有一个实例。(锁可以确保锁住的代码块是线程独占访问的,如果一个线程占有了这个锁,其它线程只能等待该线程释放锁以后才能继续访问)。

public class SingletonCounter
{private static SingletonCounter instance;private static readonly object locker = new object();private static int number = 0;private SingletonCounter() { }public static SingletonCounter Instance{get{lock (locker){if (instance == null) instance = new SingletonCounter();number++;return instance;}}}public int GetCounter(){return number;}
}

客户端调用代码:

static void Main(string[] args)
{ for (int i = 1; i < 100; i++){var task = new Task(() =>{SingletonCounter singleton = SingletonCounter.Instance;int number = singleton.GetCounter();Console.WriteLine("App  call  the counter get number was:" +  number);});task.Start();}Console.ReadKey();
}

输出结果:

image

这种模式是线程安全,即使在多线程的情况下仍然可以保持单个实例。那么这种模式会不会有什么问题呢?假如系统的访问量非常大,并发非常高,那么计数器就会是一个性能瓶颈,因为对锁会使其它的线程无法访问。在访问量不大,并发量不高的系统尚可应付,如果高访问量,高并发的情况下这样做肯定是不行的,那么有什么办法改进呢?这就是下面要讨论的“双检查加锁懒汉模式”。

3、双检查加锁懒汉模式

加锁懒汉模式虽然保证了系统的线程安全,但是却为系统带来了新能问题,主要的性能来自锁带来开销,双检查就是解决这个锁带来的问题,在锁之前再做一次 instance==null的检查,如果返回true就直接返回 单例对象了,避开了无谓的锁, 我们来看下,双检查懒汉模式代码:

public class DoubleCheckLockSingletonCounter
{private static DoubleCheckLockSingletonCounter instance;private static readonly object locker = new object();private static int number = 0;private DoubleCheckLockSingletonCounter() { }public static DoubleCheckLockSingletonCounter Instance{get{if (instance == null){lock (locker){if (instance == null){instance = new DoubleCheckLockSingletonCounter();}}}number++;return instance;}}public int GetCounter(){return number;}
}

客户端调用代码和“懒汉加锁模式”相同,输出结果也相同。

4、饿汉模式

单例模式除了我们上面讲的三种懒汉模式外,还有一种叫“饿汉模式”的实现方式,“饿汉模式”直接在Singleton类里实例化了当前类的实例,并且保存在一个静态对象中,因为是静态对象,所以在程序启动的时候就已经实例化好了,后面直接使用,因此不存在线程安全的问题。

下面是“饿汉模式”的代码实现:

public class EagerSingletonCounter
{private static EagerSingletonCounter instance = new EagerSingletonCounter();private static int number = 0;private EagerSingletonCounter() { }public static EagerSingletonCounter Instance{get{number++;return instance;}}public int GetCounter(){return number;}
}

 

五、单例模式应用场景

单例模式只有一个角色非常简单,使用的场景也很明确,就是一个类只需要、且只能需要一个实例的时候使用单例模式。

六、扩展

 

1、”饿汉模式“和”懒汉模式“的比较

”饿汉模式“在程序启动的时候就已经实例化好了,并且一直驻留在系统中,客户程序调用非常快,因为它是静态变量,虽然完美的保证线程的安全,但是如果创建对象的过程很复杂,要占领系统或者网络的一些昂贵的资源,但是在系统中使用的频率又极低,甚至系统运行起来后都不会去使用该功能,那么这样一来,启动之后就一直占领着系统的资源不释放,这有些得不偿失。

“懒汉模式“ 恰好解决了”饿汉模式“这种占用资源的问题,”懒汉模式”将类的实例化延迟到了运行时,在使用时的第一次调用时才创建出来并一直驻留在系统中,但是为了解决线程安全问题, 使用对象锁也是 影响了系统的性能。这两种模式各有各的好处,但是又各有其缺点。

有没有一种折中的方法既可以避免一开始就实例化且一直占领系统资源,又没有性能问题的Singleton呢? 答案是:有的。

2、第三种选择

“饿汉模式“类不能实现延迟加载,不管用不用始终占据内存;”懒汉式模式“类线程安全控制烦琐,而且性能受影响。我们用一种折中的方法来解决这个问题,针对主要矛盾, 即:既可以延时加载又不影响性能。

在Singleton的内部创建一个私有的静态类用于充当单例类的”初始化器“,专门用来创建Singleton的实例:

public class BestPracticeSingletonCounter
{private static class SingletonInitializer{public static BestPracticeSingletonCounter instance = new BestPracticeSingletonCounter();} private static int number = 0;private BestPracticeSingletonCounter() { }public static BestPracticeSingletonCounter Instance{get{number++;return SingletonInitializer.instance;}}public int GetCounter(){return number;}
}

这种模式兼具了”饿汉“和”懒汉“模式的优点有摒弃了其缺点,可以说是一个完美的实现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/253900.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

进程及线程通信总结

上文我们介绍了如何建立一个简单的多线程程序&#xff0c;多线程之间不可避免的需要进行通信 。相比于进程间通信来说&#xff0c;线程间通信无疑是相对比较简单的。 首先我们来看看最简单的方法&#xff0c;那就是使用全局变量&#xff08;静态变量也可以&#xff09;来进行通…

ROS multi-master——multimaster_fkie配置

多主站ROS配置和mutimaster_fkie ROS版本&#xff1a;kinetic 操作系统&#xff1a;Ubuntu 16.04。 multimaster_fkie&#xff1a;github 1网络配置 1.1路由器 设置无线路由器并连接两台计算机/机器人。为这两台计算机设置静态IP地址。相互测试ping命令和ssh。 1.2主机 …

Docker入门

1. Docker简介: docker是一个基于LXC的高级容器引擎。简单地说&#xff0c;docker是一个轻量级的虚拟解决方案&#xff0c;或者说它是一个超轻量级的虚拟机&#xff08;容器&#xff09;。 Docker是一个开源的引擎&#xff0c;可以轻松的为任何应用创建一个轻量级的、可移植的、…

Gmapping——从原理到实践

概述 在SLAM中&#xff0c;机器人位姿和地图都是状态变量&#xff0c;我们需要同时对这两个状态变量进行估计&#xff0c;即机器人获得一张环境地图的同时确定自己相对于该地图的位置。我们用x表示机器人状态&#xff0c;m表示环境地图&#xff0c;z表示传感器观测情况&#xf…

【机器学习经典算法源码分析系列】-- 逻辑回归

1.逻辑回归&#xff08;Logistic Regression&#xff09;又常被成为“逻辑斯蒂回归”&#xff0c;实质上是一个二元分类问题。 逻辑回归代价函数&#xff1a; 代价函数导数&#xff1a; Matlab实现&#xff1a; 采用matlab中自带的无约束最小化函数fminunc来代替梯度下降法&…

ROS——不同版本间ROS进行通信

在相同版本间的ROS进行通信不在赘述了&#xff0c;修改/etc/hosts文件即可。 最近项目遇到在Ubuntu16.04 与Ubuntu18.04两个系统间进行ROS通信&#xff0c;ROS版本分别为Kinetic和Melodic。配置网络后&#xff0c;两边都能够ping通&#xff0c;但是在获取ros数据是&#xff0c…

大数据开发实战:数据流图及相关数据技术

1、大数据流程图 2、大数据各个环节主要技术 2.1、数据处理主要技术 Sqoop&#xff1a;&#xff08;发音&#xff1a;skup&#xff09;作为一款开源的离线数据传输工具&#xff0c;主要用于Hadoop(Hive) 与传统数据库&#xff08;MySql,PostgreSQL&#xff09;间的数据传递。它…

跨时钟域电路设计——亚稳态及双锁存器

一、同步电路 定义&#xff1a;电路中所有受时钟控制的单元&#xff0c;全部由一个统一的时钟控制。 优点&#xff1a;在同步设计中&#xff0c;EDA工具可以保证电路系统的时序收敛&#xff0c;避免电路设计中的竞争冒险。 缺点&#xff1a;时钟树综合需要加入大量延迟单元&…

跨时钟域电路设计——单bit信号

前面提到了简单的双电平锁存器&#xff0c;下面是一些单bit同步电路。 一、慢时钟域向快时钟域 边沿检测同步器 将慢时钟域的脉冲搬移并缩小为快时钟域的脉冲。 既可以检测上升沿&#xff0c;也可以检测下降沿。 如上图&#xff0c;慢时钟下一个有效脉冲的最短周期为慢时钟的…

C语言100例01 PHP版(练习)

题目&#xff1a;有1、2、3、4个数字&#xff0c;能组成多少个互不相同且无重复数字的三位数&#xff1f;都是多少&#xff1f; 程序分析&#xff1a;可填在百位、十位、个位的数字都是1、2、3、4。组成所有的排列后再去 掉不满足条件的排列。 代码&#xff1a; 1 for($i1;$i&l…

跨时钟域电路设计——结绳法

信号从快时钟域到慢时钟域过渡时&#xff0c;慢时钟可能无法对快时钟变化太快的信号进行采样。 之前的同步器法对两个时钟间的关系有要求&#xff0c;结绳法适用于任何时钟域之间的过渡。 结绳法的原理是将快时钟信号的脉冲周期延长&#xff0c;等到慢时钟周期采样后再“解绳”…

我之理解---计时器setTimeout 和clearTimeout

今天在写个图片切换的问题 有动画滞后的问题&#xff0c;才动手去查setTimeout 和clearTimeout。之前写的图片播放器也有类似的问题&#xff0c;有自动start按钮 和stop按钮&#xff0c; 其他都正常&#xff0c;问题出在每次多次快速的点击start按钮时&#xff0c;图片播放的速…

关于二维码分块上色(彩色二维码)的算法研究

原文:关于二维码分块上色&#xff08;彩色二维码&#xff09;的算法研究众所周知&#xff0c;二维码通常是黑白的&#xff0c;而且是由若干个长方形或正方形小块平铺而成。但从人们的审美角度来看&#xff0c;常见的黑白二维码不免让人审美疲劳。本文试着从分块上色的角度对二维…

20145309信息安全系统设计基础第12周学习总结后篇

指针与声明 声明 1、C语言中变量的声明 类型&#xff08;type&#xff09; 声明符&#xff08;declarator&#xff09; 2、最简单的声明是变量 3、指针数组 指针数组是一个数组数组里的元素都是指针例&#xff1a;int *daytab[13]4、数组指针 数组指针是一个指针指针指向一个类…

跨时钟域电路设计——多bit信号FIFO

多个bit信号的跨时钟域仅仅通过简单的同步器同步时不安全的。 如下图&#xff1a; 虽然信号都同步到目的时钟域&#xff0c;可完成的功能却与设计的初衷不相符。 解决方案之一为对信号进行格雷码编码&#xff0c;但此方案只适用于连续变化的信号。另一种方案为增加新的控制信号…

WPF 打印实例

原文:WPF 打印实例在WPF 中可以通过PrintDialog 类方便的实现应用程序打印功能&#xff0c;本文将使用一个简单实例进行演示。首先在VS中编辑一个图形&#xff08;如下图所示&#xff09;。 将需要打印的内容放入同一个<Canvas>中&#xff0c;并起名为“printArea”&…

静态时序分析——基础概念

一、简述 静态时序分析是检查系统时序是否满足要求的主要手段。以往时序的验证依赖于仿真&#xff0c;采用仿真的方法&#xff0c;覆盖率跟所施加的激励有关&#xff0c;有些时序违例会被忽略。此外&#xff0c;仿真方法效率非常的低&#xff0c;会大大延长产品的开发周期。静…

静态时序分析——单周期

一、建立时间的检查 建立时间的检查是指检查电路里每一个触发器的数据和时钟的关系是否满足建立时间的要求。 我们以上图为例进行建立时间检查。由图可知&#xff0c;我们主要针对第二个触发器UFF1进行检查。我们可以梳理时序关系如下&#xff1a; 通过这个图&#xff0c;我们…

自己搭建的CISCO实验环境

交换机&#xff1a;设备型号&#xff1a; CISCO 3750 24-TS 3台CISCO 3750 48-PS 1台路由器&#xff1a;设备型号&#xff1a;1.CISCO 2821 3台2.CISCO 3745 3台 物理拓扑图如下&#xff1a; 转载于:https://blog.51cto.com/zxs3026/2156424

CMOS组合逻辑

1. 静态互补CMOS 实际上就是静态CMOS反相器扩展为具有多个输入。更反相器一样具有良好的稳定性&#xff0c;性能和功耗。 静态的概念&#xff1a;每一时刻每个门的输出通过低阻抗路径连到VDD或VSS上。任何时候输出即为布尔函数值。动态电路通常依赖把信号暂存在高阻抗节点的电…