深入浅出单实例Singleton设计模式

深入浅出单实例Singleton设计模式

陈皓

前序

单实例Singleton设计模式可能是被讨论和使用的最广泛的一个设计模式了,这可能也是面试中问得最多的一个设计模式了。这个设计模式主要目的是想在整个系统中只能出现一个类的实例。这样做当然是有必然的,比如你的软件的全局配置信息,或者是一个Factory,或是一个主控类,等等。你希望这个类在整个系统中只能出现一个实例。当然,作为一个技术负责人的你,你当然有权利通过使用非技术的手段来达到你的目的。比如:你在团队内部明文规定,“XX类只能有一个全局实例,如果某人使用两次以上,那么该人将被处于2000元的罚款!”(呵呵),你当然有权这么做。但是如果你的设计的是东西是一个类库,或是一个需要提供给用户使用的API,恐怕你的这项规定将会失效。因为,你无权要求别人会那么做。所以,这就是为什么,我们希望通过使用技术的手段来达成这样一个目的的原因。

本文会带着你深入整个Singleton的世界,当然,我会放弃使用C++语言而改用Java语言,因为使用Java这个语言可能更容易让我说明一些事情。


Singleton的教学版本

这里,我将直接给出一个Singleton的简单实现,因为我相信你已经有这方面的一些基础了。我们姑且把这具版本叫做1.0版

  1. // version 1.0   
  2. public class Singleton   
  3. {   
  4.     private static final Singleton singleton = null;   
  5.   
  6.     private Singleton()   
  7.     {   
  8.     }   
  9.     public static Singleton getInstance()   
  10.     {   
  11.         if (singleton== null)   
  12.         {   
  13.             singleton= new Singleton();   
  14.         }   
  15.         return singleton;   
  16.     }   
  17. }  
    [java] view plaincopy
    1.    

在上面的实例中,我想说明下面几个Singleton的特点:(下面这些东西可能是尽人皆知的,没有什么新鲜的)

  1. 私有(private)的构造函数,表明这个类是不可能形成实例了。这主要是怕这个类会有多个实例。
  2. 即然这个类是不可能形成实例,那么,我们需要一个静态的方式让其形成实例:getInstance()。注意这个方法是在new自己,因为其可以访问私有的构造函数,所以他是可以保证实例被创建出来的。
  3. 在getInstance()中,先做判断是否已形成实例,如果已形成则直接返回,否则创建实例。
  4. 所形成的实例保存在自己类中的私有成员中。
  5. 我们取实例时,只需要使用Singleton.getInstance()就行了。

当然,如果你觉得知道了上面这些事情后就学成了,那我给你当头棒喝一下了,事情远远没有那么简单。

Singleton的实际版本

上面的这个程序存在比较严重的问题,因为是全局性的实例,所以,在多线程情况下,所有的全局共享的东西都会变得非常的危险,这个也一样,在多线程情况下,如果多个线程同时调用getInstance()的话,那么,可能会有多个进程同时通过 (singleton== null)的条件检查,于是,多个实例就创建出来,并且很可能造成内存泄露问题。嗯,熟悉多线程的你一定会说——“我们需要线程互斥或同步”,没错,我们需要这个事情,于是我们的Singleton升级成1.1版,如下所示:

  1. // version 1.1   
  2. public class Singleton   
  3. {   
  4.     private static final Singleton singleton = null;   
  5.   
  6.     private Singleton()   
  7.     {   
  8.     }   
  9.     public static Singleton getInstance()   
  10.     {   
  11.         if (singleton== null)   
  12.         {   
  13.             synchronized (Singleton.class) {   
  14.                 singleton= new Singleton();   
  15.             }   
  16.         }   
  17.         return singleton;   
  18.     }   
  19. }  
[java] view plaincopy
  1.    

嗯,使用了Java的synchronized方法,看起来不错哦。应该没有问题了吧?!错!这还是有问题!为什么呢?前面已经说过,如果有多个线程同时通过(singleton== null)的条件检查(因为他们并行运行),虽然我们的synchronized方法会帮助我们同步所有的线程,让我们并行线程变成串行的一个一个去new,那不还是一样的吗?同样会出现很多实例。嗯,确实如此!看来,还得把那个判断(singleton== null)条件也同步起来。于是,我们的Singleton再次升级成1.2版本,如下所示:

 
  1. // version 1.2   
  2. public class Singleton   
  3. {   
  4.     private static final Singleton singleton = null;   
  5.   
  6.     private Singleton()   
  7.     {   
  8.     }   
  9.     public static Singleton getInstance()   
  10.     {   
  11.         synchronized (Singleton.class)   
  12.         {   
  13.             if (singleton== null)   
  14.             {   
  15.                 singleton= new Singleton();   
  16.             }   
  17.         }   
  18.         return singleton;   
  19.     }   
  20. }  
[java] view plaincopy
  1.    

不错不错,看似很不错了。在多线程下应该没有什么问题了,不是吗?的确是这样的,1.2版的Singleton在多线程下的确没有问题了,因为我们同步了所有的线程。只不过嘛……,什么?!还不行?!是的,还是有点小问题,我们本来只是想让new这个操作并行就可以了,现在,只要是进入getInstance()的线程都得同步啊,注意,创建对象的动作只有一次,后面的动作全是读取那个成员变量,这些读取的动作不需要线程同步啊。这样的作法感觉非常极端啊,为了一个初始化的创建动作,居然让我们达上了所有的读操作,严重影响后续的性能啊!

还得改!嗯,看来,在线程同步前还得加一个(singleton== null)的条件判断,如果对象已经创建了,那么就不需要线程的同步了。OK,下面是1.3版的Singleton。

  1. // version 1.3   
  2. public class Singleton   
  3. {   
  4.     private static final Singleton singleton = null;   
  5.   
  6.     private Singleton()   
  7.     {   
  8.     }   
  9.     public static Singleton getInstance()   
  10.     {   
  11.         if (singleton== null)   
  12.         {   
  13.             synchronized (Singleton.class)   
  14.             {   
  15.                 if (singleton== null)   
  16.                 {   
  17.                     singleton= new Singleton();   
  18.                 }   
  19.             }   
  20.         }   
  21.         return singleton;   
  22.     }   
  23. }  
[java] view plaincopy
  1.    

感觉代码开始变得有点罗嗦和复杂了,不过,这可能是最不错的一个版本了,这个版本又叫“双重检查”Double-Check。下面是说明:

  1. 第一个条件是说,如果实例创建了,那就不需要同步了,直接返回就好了。
  2. 不然,我们就开始同步线程。
  3. 第二个条件是说,如果被同步的线程中,有一个线程创建了对象,那么别的线程就不用再创建了。

相当不错啊,干得非常漂亮!请大家为我们的1.3版起立鼓掌!

 

Singleton的其它问题

 

怎么?还有问题?!当然还有,请记住下面这条规则——“无论你的代码写得有多好,其只能在特定的范围内工作,超出这个范围就要出Bug了”,这是“陈式第一定理”,呵呵。你能想一想还有什么情况会让这个我们上面的代码出问题吗?

在C++下,我不是很好举例,但是在Java的环境下,嘿嘿,还是让我们来看看下面的一些反例和一些别的事情的讨论(当然,有些反例可能属于钻牛角尖,可能有点学院派,不过也不排除其实际可能性,就算是提个醒吧):

其一、Class Loader。不知道你对Java的Class Loader熟悉吗?“类装载器”?!C++可没有这个东西啊。这是Java动态性的核心。顾名思义,类装载器是用来把类(class)装载进JVM的。JVM规范定义了两种类型的类装载器:启动内装载器(bootstrap)和用户自定义装载器(user-defined class loader)。 在一个JVM中可能存在多个ClassLoader,每个ClassLoader拥有自己的NameSpace。一个ClassLoader只能拥有一个class对象类型的实例,但是不同的ClassLoader可能拥有相同的class对象实例,这时可能产生致命的问题。如ClassLoaderA,装载了类A的类型实例A1,而ClassLoaderB,也装载了类A的对象实例A2。逻辑上讲A1=A2,但是由于A1和A2来自于不同的ClassLoader,它们实际上是完全不同的,如果A中定义了一个静态变量c,则c在不同的ClassLoader中的值是不同的。

于是,如果咱们的Singleton 1.3版本如果面对着多个Class Loader会怎么样?呵呵,多个实例同样会被多个Class Loader创建出来,当然,这个有点牵强,不过他确实存在。难道我们还要整出个1.4版吗?可是,我们怎么可能在我的Singleton类中操作Class Loader啊?是的,你根本不可能。在这种情况下,你能做的只有是——“保证多个Class Loader不会装载同一个Singleton”。

其二、序例化。如果我们的这个Singleton类是一个关于我们程序配置信息的类。我们需要它有序列化的功能,那么,当反序列化的时候,我们将无法控制别人不多次反序列化。不过,我们可以利用一下Serializable接口的readResolve()方法,比如:

  1. public class Singleton implements Serializable   
  2. {   
  3.     ......   
  4.     ......   
  5.     protected Object readResolve()   
  6.     {   
  7.         return getInstance();   
  8.     }   
  9. }  
[java] view plaincopy
  1.    

其三、多个Java虚拟机。如果我们的程序运行在多个Java的虚拟机中。什么?多个虚拟机?这是一种什么样的情况啊。嗯,这种情况是有点极端,不过还是可能出现,比如EJB或RMI之流的东西。要在这种环境下避免多实例,看来只能通过良好的设计或非技术来解决了。

其四,volatile变量。关于volatile这个关键字所声明的变量可以被看作是一种 “程度较轻的同步synchronized”;与 synchronized 块相比,volatile 变量所需的编码较少,并且运行时开销也较少,但是它所能实现的功能也仅是synchronized的一部分。当然,如前面所述,我们需要的Singleton只是在创建的时候线程同步,而后面的读取则不需要同步。所以,volatile变量并不能帮助我们即能解决问题,又有好的性能。而且,这种变量只能在JDK 1.5+版后才能使用。

其五、关于继承。是的,继承于Singleton后的子类也有可能造成多实例的问题。不过,因为我们早把Singleton的构造函数声明成了私有的,所以也就杜绝了继承这种事情。

其六,关于代码重用。也话我们的系统中有很多个类需要用到这个模式,如果我们在每一个类都中有这样的代码,那么就显得有点傻了。那么,我们是否可以使用一种方法,把这具模式抽象出去?在C++下这是很容易的,因为有模板和友元,还支持栈上分配内存,所以比较容易一些(程序如下所示),Java下可能比较复杂一些,聪明的你知道怎么做吗?

  1. template<CLASS T> class Singleton   
  2. {   
  3.     public:   
  4.         static T& Instance()   
  5.         {   
  6.             static T theSingleInstance; //假设T有一个protected默认构造函数   
  7.             return theSingleInstance;   
  8.         }   
  9. };   
  10.   
  11. class OnlyOne : public Singleton<ONLYONE>   
  12. {   
  13.     friend class Singleton<ONLYONE>;   
  14.     int example_data;   
  15.   
  16.     public:   
  17.         int GetExampleData() const {return example_data;}   
  18.     protected:   
  19.         OnlyOne(): example_data(42) {}   // 默认构造函数   
  20.         OnlyOne(OnlyOne&) {}   
  21. };   
  22.   
  23. int main( )   
  24. {   
  25.     cout << OnlyOne::Instance().GetExampleData()<< endl;   
  26.     return 0;   
  27. }  
[c++] view plaincopy
  1. 本文同时发表于——酷壳:<a href="http://cocre.com/?p=265">http://cocre.com/?p=265</a>  

 (转载时请注明作者和出处。未经许可,请勿用于商业用途)

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

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

相关文章

一直追求技术好吗?

我在大一的时候开始接触了编程&#xff0c;一直到现在&#xff0c;都是个技术的狂热追求者。从最开始的数据结构到现在的设计模式&#xff0c;经常是一连好几天一直在看资料、写代码&#xff0c;疯狂得连做梦也在想着怎么写代码。我同时也是个新技术的追随着&#xff0c;javasc…

【转】04.Dicom 学习笔记-DICOM C-Move 消息服务

引言 前篇介绍了 DICOM C-Get 消息服务&#xff0c;本文结合开源 DICOM 库 fo-dicom 详细介绍一下 C-Move 服务。 C-Move 消息服务 C-Move 服务可以用来获取影像和转存影像&#xff0c;用于一个 DIMSE-service-user 在同等的 DIMSE-service-user 上查询复合 SOP 实例的属性满足…

linux rar命令没找到,Linux没有rar解压命令

使用微软系统的同学都知道rar解压缩软件&#xff0c;它是一个商业的收费软件&#xff0c;那在免费开源的linux系统怎么解压后缀为rar的文件呢&#xff0c;我相信很多同学都是先把rar的文件下载到电脑本地&#xff0c;然后再用7-zip或者winrar 等等解压缩软件解压再压缩为zip的文…

PID是什么?在做系统的故障排除时如何使用它?

PID Process Identifier, 是一个全局唯一的用来标识进程的整数。在多任务系统中&#xff0c;可用来诊断系统中发生错误的进程。转载于:https://www.cnblogs.com/zhangliang2121/archive/2008/09/17/1292510.html

【转】05.Dicom 学习笔记-DICOM C-Echo 消息服务

引言 经过前面几篇的介绍&#xff0c;DIMSE-C 消息服务这块已经讲解了差不多了&#xff0c;还剩最后一个 C-Echo 消息服务&#xff0c;这个服务相对前面的4个服务来说更简单一些&#xff0c;本文结合开源 DICOM 库 fo-dicom 详细介绍一下 C-Echo 服务。 C-Echo 消息服务 首先来…

查看linux不显示ip,linux系统查看IP地址,不显示IP地址或者只显示127.0.0.1

LUA学习笔记三&&num;183&semi;时间等操作系统库1.构造时间 2.时间制定格式输出 3.计时器(闭包)os.difftime (t2, t1) 返回以秒计算的时刻 t1 到 t2 的差值. (这里的时刻是由 os.time 返回的值). 在 POS ...String定义与方法//5种构造方法 public void Con(){ Strin…

哪些设计模式最值得学习

最近又在首页看到几篇设计模式相关的学习随笔。回想起来&#xff0c;这几年在园子里发布的有关设计模式的随笔都有一个共同的特点。那就是Factory和Singleton居多&#xff0c;如果是系列的&#xff0c;也往往是从这两个模式开始的。由于能够坚持把《设计模式》中所有模式都写完…

【转】000.DICOM:DICOM标准学习路线图(初稿)!!!!!!!!!!!!

转自&#xff1a;https://zssure.blog.csdn.net/article/details/49231303 题记&#xff1a; DICOM医学图像处理专栏撰写已有两个年头&#xff0c;积累了近百篇文章。 起初 只是用于记录自己科研、工作中遇到的疑难问题&#xff0c;专注于图像处理&#xff08;主要是医学图像…

linux mysql关闭启动不了了,linux启动或关闭mysql失败的解决办法

linux启动或关闭mysql时提示&#xff1a;Warning: World-writable config file /etc/my.cnf is ignored.什么鬼&#xff1f; 意思是&#xff1a; 警告&#xff1a;全世界都能写的一个配置文件已经被我给忽略了。什么要忽略&#xff1f;因为mysql觉得他太low了&#xff0c;不安全…

对比 SQL Server 2005 和 Oracle

在 Microsoft Windows Server 上运行的 SQL Server 2005&#xff0c;为企业级关系数据库和分析解决方案提供了一个平台&#xff0c;在安全、可用性、与 Visual Studio 的整合度&#xff0c;从小企业到大企业的可扩展性以及低费用方面胜过了Oracle 10g。探索下列信息以发现在 这…

C++设计模式之四 模板模式

《TemplateMethod1.h》 #include <iostream> #include <string> using namespace std;class A{public:A(){};virtual ~A(){};void Method();protected:virtual void b() 0;virtual void c() 0;};class B: public A{public:B(){};virtual ~B(){};protected:voi…

【转】pacs定位线_C#开发PACS医学影像处理系统(十五):Dicom影像交叉定位线算法

转自&#xff1a;https://www.cnblogs.com/Uncle-Joker/p/13686618.html 1.定位线概念&#xff1a;某个方位的影像在另一个方向的影像上的投影相交线&#xff0c;例如横断面(从头到脚的方向)在矢状面(从左手到右手)上的影像投影面交线。 举个例子&#xff1a;右边的是MR(核磁共…

C++设计模式之 简单工厂模式讲解(历史上最简单明白的例子)

工作之余&#xff0c;在看资料过程中发现一个极易理解的简单工厂模式的例子&#xff0c;自己亲自试练一番,感觉对这个设计模式不熟悉的朋友&#xff0c; 一看马上就知道是什么回事了。 简单工厂模式根据提供给它的数据&#xff0c;返回几个可能类中的一个类的实例。通常它返的…

关于html和javascript在浏览器中的加载顺序问题的讨论(zz)

前一阵子横扫了javascript&#xff0c;当时自我感觉良好。现在一想&#xff0c;又觉得没什么。今天的任务是把asp.net ajax中客户端页面生命周期那一章研究完。然而&#xff0c;因为这一章的内容使我产生了一些迷惑。这些疑惑在书中都没有只字提及。 一、html页面的详细加载过程…

linux中sybase删除数据库,Linux_Sybase ASE数据库的常见问题解答,1 数据库占用磁盘空间的形式 - phpStudy...

Sybase ASE数据库的常见问题解答1 数据库占用磁盘空间的形式是什么&#xff1f;Master数据库的作用是什么&#xff1f;如果master失败后果如何&#xff1f;设备文件. 存储系统表, 系统将无法使用, 除非重建master库2 段的实质是什么&#xff1f;已被SYBASE中对象使用的段可否被…

【转】.NET Core 可移植类库PCL Portable Class Library详解

转自&#xff1a;https://www.kaifaxueyuan.com/server/dotnet-core/dotnet-core-portable-class-library.html 在这一章中&#xff0c;我们将讨论什么是PCL (可移植类库)&#xff0c;以及为什么我们需要PCL。为了理解这个概念&#xff0c;让我们打开上一章创建的类库项目文件夹…

sql表的所有者

今天建立了一个表&#xff0c;名为tmp_pactrebate,默认的所有者为dbo,如果想改成所有者是自己的名字&#xff0c;例如&#xff1a;wcj 在查询分析器中执行这句话即可 EXEC sp_changeobjectowner dbo.tmp_pactrebate, wcj 改后的表名为:wcj.tmp_pactrebate select * from wcj.tm…

loadrunner linux 端口,Linux下loadrunner generator的安装

Loadrunner linux generator version&#xff1a;9.5Step1&#xff1a;建立一个普通用户loadrunner&#xff0c;目录&#xff1a;/home/loadrunner&#xff0c;将安装文件考到/home/loadrunner下面&#xff0c;但是需要使用root用户安装&#xff0c;./installer.sh –consoleSt…

设计模式C++实现(4)——原型模式、模板方法模式

软件领域中的设计模式为开发人员提供了一种使用专家设计经验的有效途径。设计模式中运用了面向对象编程语言的重要特性&#xff1a;封装、继承、多态&#xff0c;真正领悟设计模式的精髓是可能一个漫长的过程&#xff0c;需要大量实践经验的积累。最近看设计模式的书&#xff0…

【转】医疗业务学习笔记--DICOM协议的基础内容!!!!!!!!!!

转自&#xff1a;医疗业务学习笔记--DICOM协议的基础内容 - 知乎 本文首发于“雨夜随笔”公众号&#xff0c;欢迎关注。 DICOM协议是医疗领域对如何处理、存储、打印和传输医疗图片的一系列标准。DICOM是 Digital Imaging and Communications in Medicine 的缩写&#xff0c;…