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

一、单例模式定义

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

二、单例模式结构图

  • 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();
}

输出结果:

这个实现是线程不安全的,如果有多个线程同时调用,并且又恰恰在计数器初始化的瞬间多个线程同时检测到了 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();
}

输出结果:

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

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;
}
}

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

【设计模式】单例模式 Singleton Pattern的更多相关文章

  1. 浅谈设计模式--单例模式(Singleton Pattern)

    题外话:好久没写blog,做知识归纳整理了.本来设计模式就是个坑,各种文章也写烂了.不过,不是自己写的东西,缺少点知识的存在感.目前还没做到光看即能记住,得写.所以准备跳入设计模式这个大坑. 开篇先贡 ...

  2. 设计模式之单例模式(Singleton Pattern)

    单例模式 单例模式(Singleton Pattern)在java中算是最常用的设计模式之一,主要用于控制控制类实例的数量,防止外部实例化或者修改.单例模式在某些场景下可以提高系统运行效率.实现中的主 ...

  3. 乐在其中设计模式(C#) - 单例模式(Singleton Pattern)

    原文:乐在其中设计模式(C#) - 单例模式(Singleton Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 单例模式(Singleton Pattern) 作者:weba ...

  4. 设计模式系列之单例模式(Singleton Pattern)——确保对象的唯一性

    模式概述 模式定义 模式结构图 饿汉式单例与懒汉式单例 饿汉式单例 懒汉式单例 模式应用 模式在JDK中的应用 模式在开源项目中的应用 模式总结 主要优点 适用场景 说明:设计模式系列文章是读刘伟所著 ...

  5. 二十四种设计模式:单例模式(Singleton Pattern)

    单例模式(Singleton Pattern) 介绍保证一个类仅有一个实例,并提供一个访问它的全局访问点. 示例保证一个类仅有一个实例. Singleton using System; using S ...

  6. 抽象工厂(Abstract Factory),工厂方法(Factory Method),单例模式(Singleton Pattern)

    在谈工厂之前,先阐述一个观点:那就是在实际程序设计中,为了设计灵活的多态代码,代码中尽量不使用new去实例化一个对象,那么不使用new去实例化对象,剩下可用的方法就可以选择使用工厂方法,原型复制等去实 ...

  7. 十次艳遇单例设计模式(Singleton Pattern)

    1.引言 单例设计模式(Singleton Pattern)是最简单且常见的设计模式之一,在它的核心结构中只包含一个被称为单例的特殊类.通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访 ...

  8. 设计模式 单例模式(Singleton) [ 转载2 ]

    设计模式 单例模式(Singleton) [ 转载2 ] @author java_my_life 单例模式的结构 单例模式的特点: 单例类只能有一个实例. 单例类必须自己创建自己的唯一实例. 单例类 ...

  9. 设计模式 单例模式(Singleton) [ 转载 ]

    设计模式 单例模式(Singleton) [ 转载 ] 转载请注明出处:http://cantellow.iteye.com/blog/838473 前言 懒汉:调用时才创建对象 饿汉:类初始化时就创 ...

随机推荐

  1. Qt之QDomDocument操作xml文件-模拟ini文件存储

    一.背景 不得不说Qt是一个很强大的类库,不管是做项目还是做产品,Qt自身封装的东西就已经非常全面了,我们今天的这篇文章就是模拟了Qt读写ini文件的一个操作,当然是由于一些外力原因,我们决定自己来完 ...

  2. python——矩阵的奇异值分解,对图像进行SVD

    矩阵SVD 奇异值分解(Singular Value Decomposition)是一种重要的矩阵分解方法,可以看做是对方阵在任意矩阵上的推广.Singular的意思是突出的,奇特的,非凡的,按照这样 ...

  3. SpringCloud-分布式链路跟踪配置详解

    SpringCloud-分布式链路跟踪 作者 : Stanley 罗昊 [转载请注明出处和署名,谢谢!] 注:作者使用IDEA + Gradle 注:需要有一定的java SpringBoot and ...

  4. OO第二单元作业小结

    前言 转眼已是第九周,第二单元的电梯系列作业已经结束,终于体验了一番多线程电梯之旅. 第一次作业是单电梯的傻瓜调度,虽然是第一次写多线程,但在课程PPT的指引下,写起来还是非常容易:第二次作业是单电梯 ...

  5. Django Admin管理入门

    Django最强大的部分之一是自动管理界面.它从模型中读取元数据,以提供快速,以模型为中心的界面,受信任的用户可以在其中管理您网站上的内容.管理员的推荐用途仅限于组织的内部管理工具.它不是用于构建整个 ...

  6. openlayers4 入门开发系列之风场图篇

    前言 openlayers4 官网的 api 文档介绍地址 openlayers4 api,里面详细的介绍 openlayers4 各个类的介绍,还有就是在线例子:openlayers4 官网在线例子 ...

  7. 通过 bsondump 命令工具 解析备份产生的bson文件

    bsondump命令是将BSON格式的文件转换为可读性更强的文件格式,例如转为为JSON 格式的文档,bsondump默认转换为json格式的文档. 当通过mongodump命令进行备份时,如果有参数 ...

  8. SQL Server 创建跨库查詢、修改、增加、删除

    一.通过SQL语句访问远程数据库   --OPENROWSET函数 使用OPENROWSET()是个不错的选择,也可以用做跨库查询包括增.删.改.查 下面就来介绍一下OPENROWSET函数的运用 包 ...

  9. 微信小程序客服消息实时通知之最佳实践

    我们做微信小程序开发的都知道,只要在小程序页面中添加如下代码即可进入小程序的客服会话界面: <button open-type="contact" >联系我们</ ...

  10. JDK、JRE、JVM三者间的联系与区别

    有Java编程经验的程序员应该都知道:Java程序是运行在JVM(Java虚拟机)上的,在开发程序之前都要配置Java开发环境,其中首先要做的就是JDK的安装和配置,那么JDK.JVM.JRE到底有何 ...