设计模式——单例模式(学习笔记)

       最近在学习设计模式,并决定把学习的东西写成笔记,也算是心得了吧。

——前言

定义:单例模式就是当你希望在你的工程中有个只会实例化一次的类(或者说只有一个对象)时,需要使用的设计模式。既然使用这种模式的类只可能有一个对象,自然要提供一个随时访问的全局访问点。

    用更专业点的话说,单例模式确保类仅有一个实例,并该类提供了一个全局访问点。

实现:

单线程

对于一个单线程的程序来说,实现单例模式是很容易的,实现的技巧就是,将构造器设置为私有的(没学单例之前我都不知道可以这个样子),这样该类就只能在本类中实例化。再定义一个静态方法允许在其他类中获取该实例。而根据实例化的方式,可以分为两种,主动实例化和被动实例化(对应的也称之为饿汉式和懒汉式,个人更喜欢前面那种的名字);

主动实例化(饿汉式):被加载时就将自己实例化。为静态实例化方式。它在静态初始化期间或在类的构造器中分配变量。类一加载就实例化,所以要提前占用系统资源。当这个类初始化要占用很多资源时,这么写就有点囧了。java源码:

publicclass Singleton {

//唯一私有静态引用,类一加载就实例化

privatestatic Singleton uniqueInstance =new Singleton();

/**

       * 注意这是一个私有构造器 ,也就是说单例模式只能自己实例化自己

        */

privateSingleton() {

//初始化

}

//提供了一个全局访问点

publicstatic Singleton instance() {

  returnuniqueInstance;

}

}

被动实例化(懒汉式):在第一次调用instance()方法时,对该类进行实例化。java源码:

public class Singleton {

      // 唯一私有静态引用

      private static Singleton uniqueInstance =null;

/**

       * 注意这是一个私有构造器 ,也就是说单例模式只能自己实例化自己

        */

      private Singleton() {

//初始化

}

//提供了一个全局访问点,在多线程中,需要加一个关键字:synchronized

  public static Singleton instance() {

         if(uniqueInstance == null)

uniqueInstance = new Singleton();

         return uniqueInstance;

      }

}


两种实例化方式的比较:

相比较而言,主动实例化(饿汉式)比较占用资源,但是由于加载时即初始化,性能会较被动实例化(饿汉式)要好一些。在多线程的环境下,主动实例化不会有线程安全问题,被动实例化(饿汉式),初始过程中,当多个线程同时访问时,则有可能获得多个实例。

多线程

上述被动实例化的源代码,多线程中可能会有多个实例的问题,在java语言中,我们可以加一个同步关键字synchronized解决,但是性能上,就会差很多。因为只有在实例化的时候才需要同步,在实例化之后,这个同步就成为了无用的且昂贵的开销。对此,有些人提出了在c++语言中常用的双重上锁成例来解决。

下面先解释一下c++中的双重上锁成例,源代码:

class Singleton:  

{  

public:  

//全局访问点,使用双重上锁成例(个人认为应该叫做双重判断)

static CSingleton * GetInstance()  

{

if (m_pInstance == NULL)  

{

Lock();//伪代码,我并不知道在c++中如何上锁

if(m_pInstance == NULL)

{

m_pInstance = new CSingleton();  

}

return m_pInstance;  

}

}

private:  

CSingleton(//初始化){};  //与java相同的私有构造函数

static CSingleton * m_pInstance; 

class CGarbo // 它的唯一工作就是在析构函数中删除CSingleton的实例  

{  

public:  

~CGarbo()  

{  

if (CSingleton::m_pInstance)  

delete CSingleton::m_pInstance;  

}  

};   

static CGarbo Garbo; // 定义一个静态成员,在程序结束时,系统会调用它的析构函数  

很遗憾,我并未在c++编程涉及过多线程,也未涉及到如何上锁,所以上述只能用伪代码写上锁了。下面解释一下为什么要双重上锁,和这个单例类的CGarbo内部类是做什么的。

双重上锁:

很显然,在多线程的情况下,可能有多个线程同时到达代码处,之后上锁。如果没有代码处的判断,那么多个线程依然会逐次的生成多个实例。反之,加上判断之后,第二个及以后的线程在代码处判断为假,直接返回已有的对象。

C++的内存回收:

C++中内存需要自己管理,那么这个私有静态变量m_pInstance指向的空间该如何释放?!

放到析构函数里是不成立的。

问:m_pInstance指针指向的对象什么时候会调用析构函数?

答:在我执行delete m_pInstance的时候。。。那我都显式调用了,自然会释放资源,要析构函数做什么?!(当然,如果该单例中还有其他资源需要释放的话,析构函数还是必要的),这种额外的显示调用容易被人遗忘,更可怕的是,如果有其他线程需要使用这个对象,让你给提前释放了。。。那就是个大的bug.

有人用了特别巧妙的方法,让让这个类自己知道在合适的时候把自己删除。C++中,程序在结束的时候,系统会自动析构所有的全局变量和类的静态成员变量。利用这个特征,我们可以在单例类中定义一个这样的静态成员变量,而它的唯一工作就是在析构函数中删除单例类的实例,即也就是上面我们看到的CGarbo内部类,(Garbo意为垃圾工人)。


遗憾的是,由于java语言规范与c++不同,java中,双重上锁成例不可实现,具体是由于什么java允许无序写入(我是没太弄懂),具体解释见如下网址:

中文:http://www.ibm.com/developerworks/cn/java/j-dcl.html

英文:http://www.ibm.com/developerworks/java/library/j-dcl/index.html


需要指出的是,主动实例化(饿汉式)在c++中比较难实现,因为静态初始化在c++中没有固定的顺序,因而静态的uniqueInstance变量的初始化与类的加载顺序没有保证,可能会出问题。所以在c++中经常使用被动实例化。而java,则常常使用主动实例化。

总结:

我们看到,如果不是在多线程中,单例模式可以说是异常的简单(多线程也不难),多线程的话,java建议使用主动实例化(饿汉式),被动(懒汉式)的话,要加同步关键字。C++中,多使用被动实例化。在c++中,很多人使用这种方法 http://blog.csdn.net/aaa20090987/article/details/7385081 来实现,好像是很好的方法。

什么时候使用单例模式?

单例模式不难,不代表它容易使用。关键是什么时候使用单例模式。以个人的理解,那些你觉得在工程中需要由“一个人”完成的工作,多使用单例模式。举一个网上常见的例子:一台计算机可以有多台打印机,而这个计算机上又可能有多个进程(或线程)想要使用打印机。这时你就希望有这么“一个人”,来统一的管理打印机,而不会出乱子,这就可以使用单例模式。同理,我们的程序中的配置类也通常是单例模式的,资源管理也通常是单例模式。网上说单例模式还可以作为通信媒介使用,也就是数据共享,它可以在不建立直接关联的条件下,让多个不相关的两个线程或者进程之间实现通信(我觉得这个比较牵强)。

当然,还有其他你觉得符合单例模式好处的场合。

单例模式的好处:

控制只有一个实例,节省资源;


单例模式要注意:

1.多线程时,除实例化外,要注意操作的线程安全。

2.据说不可以使用反射机制

3.据说也不可以序列化

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值