辛格尔顿和Android

辛格尔顿(Singleton)

一个、  什么是单例模式

单例模式。简单点来说就是设计一个类,使其在不论什么时候,最多仅仅有一个实例,并提供一个訪问这个实例的全局訪问点。


二、  为什么要单例

在程序中的非常多地方。仅仅有一个实例是非常重要的。比如,在windows中。任务管理器仅仅有一个。不管你点击多少次打开任务管理器,任务管理器也仅仅会生成一个窗体。再比如,在一些软件中,工具箱是唯一的,不管你点击多少次打开工具箱。工具箱也仅仅一个。

 

为什么要这样设计呢?由于像任务管理器或工具箱这种程序,仅仅要有一个就足够完毕全部的工作了。多个程序仅仅会白白消耗系统资源,而像任务管理器这类的程序还会引入多个任务管理器之间的同步问题。所以对些这些程序来说。仅仅有一个实例或程序是必要的。


三、  为什么须要单例模式

上面讲到对于某些程序来说。保持其仅仅有一个实例是必要的,可是怎样保证一个程序或一个类仅仅有一个实例呢?以下从类的角度来讲解。

 

第一种方法。我们抛开设计模式这个概念,假设你之前全然不知道这个概念。面对这个设计要求你会怎样做?我们能够使用一个全局的类指针变量,初始值为NULL。每当须要创建该类的对象时,都检查该指针是否为NULL。若为NULL,则使用new创建新的对象,并把对象的指针赋值给该全局指针变量。

若该指针不为NULL。则直接返回该指针或使用该指针。

这个可能是最easy想到的方法。

 

另外一种方法,就是使用单例模式。单例模式通过在类内维护一下指向该类的内部的指针,并把其构造函数声明为private或protected来阻止一般的实例化。而使用一个static的公有成员函数来实现和控制类的实例化。

在该static公有成员函数中推断该类的静态成员指针是否为NULL,若为NULL。则创建一个新的实例。并把该类的静态成员指针指向该实现。若该静态成员指针不为NULL,则直接返回NULL。

若这里看得不是非常明确,不要紧,看了以下的类图和代码自会明确。

 

相比之下,另外一种方法比第一种方法好在哪里呢?首先。第一种做法并没有强制一个类仅仅能有一个实例,一切的控制权事实上在使用者的设计中。而另外一种做法,则是类的设计者做好的,与使用者并没有关系。

换句话来说,假设使用第一种做法,则仅仅要使用者愿意。该类能够有无数个实例,而对于另外一种方法。不管使用都是否愿意,它仅仅能有一个实例。这就好比我们去吃饭,第一种方法须要顾客来推断哪些菜已经卖完。而另外一种方法由餐馆的推断哪些菜已经卖完,显然在生活中,另外一种方法才是合理的。


四、  单例模式的类图



五、  单例模式的实现(C++实现)

1singleton.h,定义类的基本成员及接口

#ifndef SINGLETON_H_INCLUDE
#define SINGLETON_H_INCLUDE
 
class Singleton
{
    public:
       static Singleton*getInstance();
       voidreleaseInstance();
 
    private://function
       Singleton(){}
       ~Singleton(){}
 
    private://data
       static Singleton*_instance;
       static unsigned int_used;
};
#endif

2singleton.cpp。实现getInstance方法和releaseInstance方法

#include "singleton.h"
 
Singleton* Singleton::_instance(0);
unsigned int Singleton::_used(0);
 
Singleton* Singleton::getInstance()
{
    ++_used;
    if(_instance == 0)
    {
       _instance = newSingleton();
    }
    return _instance;
}
 
void Singleton::releaseInstance()
{
    --_used;
    if(_used == 0)
    {
       delete _instance;
       _instance = 0;
    }
}

代码分析:

从上面的类图和代码实现能够看到。在单例模式中。我们把类的构造函数声明为私有的,从而阻止了在类外实例化对象。既然在类外不能实例化对象,那么我们怎样实例化该类呢?我们知道static成员是随类而存在的。并不随对象而存在,所以我们利用一个公有的static函数,由它来负责实现化该类的对象。由于该static函数是该类的成员函数,它能够訪问该类的private的构造函数,它也就是我们之前所说的全局訪问点。


由于可能有多个对象都引用该单例类的对象,而该对象仅仅有一个,所以肯定会有多个指针变量指向堆中同一块内存,若当中一个指针把该堆内存delete掉,然而其它的指针并不知道它所引用的对象已经不存在,继续引用该对象必定会发生段错误,为了防止在类的外部调用delete。在这里把析构函数声明为private,从而让在类外delete一个指向该单例类对象指针的操作非法。


可是C++的堆内存全然由程序猿来管理,假设不能delete的话。该对象就会在堆内存中一直存在,所以在此引入了一个方法releaseInstance和引用计数,当不再须要使用该对象时调用releaseInstance方法,该方法会把引用计数减1,当全部代码都不须要使用该对象时释放该对象,即当引用计数为0时,释放该对象。


六、  多线程下的单例模式

上面的代码在多线程环境下会引发问题。举个样例。就是当两个线程同一时候调用getInstance函数时。若该类还没有被实例化,则两个线程读取到的_instance为0。那么两个线程都会new一个新的对象,从而让该类有两个实例。同一时候,对_used的操作也会存在相同的问题,此时_use为1。

所以,显然该设计在多线程下是不安全的。

 

为了解决上述问题,我们须要为函数getInstance和releaseInstance中对_instance和_used的訪问加锁。

为了简便。仅仅列出部分关键代码。改动后的代码例如以下所看到的:(源码文件为singlton_thread.h和singleton_thread.cpp)

pthread_mutex_tSingleton::_mutex(PTHREAD_MUTEX_INITIALIZER);
 
Singleton* Singleton::getInstance()
{
    pthread_mutex_lock(&_mutex);
    ++_used;
    if(_instance== 0)
    {
       _instance= new Singleton();
    }
    pthread_mutex_unlock(&_mutex);
    return_instance;
}
 
void Singleton::releaseInstance()
{
    pthread_mutex_lock(&_mutex);
    --_used;
    if(_used== 0)
    {
       delete_instance;
       _instance= 0;
    }
    pthread_mutex_unlock(&_mutex);
}

代码分析:

从上面的代码能够看出,每次申请调用get/releaseInstance函数都会加锁和解锁,而加锁和解锁都是比較耗时的操作,所以上述的代码效率事实上并不高。

 

在一些设计模式的书上,会看到使用双if的推断来解决多次上锁的问题,可是这种方法在这里是不现实的,由于这种方法不能解决_used的訪问问题。也就是说。即使对_instance的訪问能够使用双if语句来大大降低加锁和解锁的操作。可是对_used的++和--操作相同须要加锁进行。而那些书上之所以能够使用双if来解决问题。是由所使用的语言决定的,比如使用java或c#,它们不须要对内存进行管理,所以不会存在上面代码中所出现的引用计数_used,所以双if的方法才行得通。

 

若想在C++中实现双if的推断,则不使用引用计数来管理内存就可以。即对象一旦分配就一直存在于堆内存中。

此时C++也不存在引用计数问题。不须要释放内存,因而也就不须要上面的releaseInstance方法。

其getInstance方法的实现例如以下:

Singleton* Singleton::getInstance()
{
	if(_instance == 0)
	{
		pthread_mutex_lock(&_mutex);
		if(_instance == 0)
			_instance = new Singleton();
		pthread_mutex_unlock(&_mutex);
	}
	return _instance;
}


这样就能够仅仅加锁和解锁一次,大大提高时间效率。可是对象一旦分配内存,内存就不会被释放。所以在C++中使用哪种实现策略,取决于你对时间和空间的取舍。若时间更重要。则採用后一种方法,若空间更重要,则採用前一种方法。


七、  Android中的单例模式

Android中存在着大量的单例类,如:InputMethodManager类,CalendarDatabaseHelper类、Editable类等等。在这些类中,都存在一个方法getInstance,在该方法或直接返回对象的引用或推断一个类的引用是否为NULL。若不为NULL,则直接返回该引用,若为NULL,则new一个新的对象,并返回。比如。对于CalendarDatabaseHelper类,存在例如以下的代码:

public static synchronized CalendarDatabaseHelper getInstance(Contextcontext)
{
    if (sSingleton == null)
    {
       sSingleton = newCalendarDatabaseHelper(context);
    }
        return sSingleton;
}

从这里的代码能够看出,事实上现方式与上面所说的非常类似,只是由于java不用程序猿自己管理内存,所以并不须要使用引用计数,而该方法是公有static的。而synchronized就是为了保证同一时刻仅仅能有一个线程进入该方法,这也就是防止上面第六点讲到的单例模式在多线程中的安全问题。


八、  源码地址

C++源码地址:http://download.csdn.net/detail/ljianhui/7464147


版权声明:本文博客原创文章。博客,未经同意,不得转载。

原文地址:https://www.cnblogs.com/lcchuguo/p/4707916.html