设计模式之单例模式(Singleton Pattern)
在软件系统中,经常有这样一些特殊的类,必须保证它们在系统中只存在一个实例,才能确保它们的逻辑正确性、以及良好的效率。
通常我们有以下需求时,我们会用单例设计模式来构造我们的类:
(1)当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时。
(2)当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。
Singleton有两种模式:
(1)程序启动立即实例化对象——饿汉式
这种模式有个好处就是不会出现线程问题,但是将会有内存的损耗,特别当Singleton类占用内存特别大的时候,这种方式非常不可取,所以通常用第二种模式,来设计单例类。
(2)在需要时实例化对象——懒汉式
Java代码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public class Singleton { private static Singleton singleton = null; private Singleton(){ } public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) singleton = new Singleton(); } } return singleton; } }
C#代码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public class Singleton { //关于volatile关键字: /* volatile的意思是“易变的”。键字volatile,如果去掉这个关键字,还是有可能发生线程不是安全的。 volatile 保证严格意义的多线程编译器在代码编译时对指令不进行微调。 */ /* volatile总是与优化有关,编译器有一种技术叫做数据流分析,分析程序中的变量在哪里赋值、在哪里使用、在哪里失效, 分析结果可以用于常量合并,常量传播等优化,但有时这些优化不是程序所需要的。 volatile的字面含义是易变的,它有下面的作用: 1.不会在两个操作之间把volatile变量缓存在寄存器中。在多任务、中断、甚至setjmp环境下,变量可能被其他的程序改变, 编译器 自己无法知道,volatile就是告诉编译器这种情况; 2.不做常量合并、常量传播等优化; 3.对volatile变量的读写不会被优化掉。 一般会在以下情况中添加volatile关键字: 1.中断服务程序中修改的供其它程序检测的变量需要加volatile; 2.多任务环境下各任务间共享的标志应该加volatile; 3.存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义。 */ private static volatile Singleton singleton = null; private static object lockHelper = new object(); private Singleton() { } public static Singleton Instance { get { if (singleton == null) { lock (lockHelper) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } } }
上面代码中无论是Java代码还是C#代码,都判断了两次singleton字段是否为空,这是为了在多线程环境中,既保证线程安全又保证代码的质量,下面我们来分析一下这种写法的好处。
(1)两段代码中synchronized (Singleton.class)和lock (lockHelper)都是以某一对象为线程锁在代码中形成同步代码块,保证同一时间线内只有一个线程在这段代码块中执行代码,保证了singleton对象只会有一个线程能将其实例化。
(2)两次判断singleton对象是否为空的过程中,第一次就是为了判断singleton是否为空,然后如果为空,则第一个进入判断代码块的线程就会建立线程锁,进入singleton实例化的代码块中执行实例化,而其他的线程就会阻塞,当第一个线程实例化完成后,其他的线程才会进入实例化的代码块中,再次判断singleton时候为空,这就保证了singleton在多线程环境中只会实例化一次。接下来,其他的线程在Java中进入getIntance方法中或者在C#中获取Instance属性时都不会进入判断singleton为空的判断语句的代码块中,因为singleton已经被实例化,其并不为null,所以就没有了判断线程锁的过程,而这个过程如果每次都要进行的话将消耗大量的cpu资源,所以这样就减少了cpu资源的消耗,提高了效率。