辛格尔顿(Singleton)
一个、 什么是单例模式
单例模式。简单点来说就是设计一个类,使其在不论什么时候,最多仅仅有一个实例,并提供一个訪问这个实例的全局訪问点。
二、 为什么要单例
在程序中的非常多地方。仅仅有一个实例是非常重要的。比如,在windows中。任务管理器仅仅有一个。不管你点击多少次打开任务管理器,任务管理器也仅仅会生成一个窗体。再比如,在一些软件中,工具箱是唯一的,不管你点击多少次打开工具箱。工具箱也仅仅一个。
为什么要这样设计呢?由于像任务管理器或工具箱这种程序,仅仅要有一个就足够完毕全部的工作了。多个程序仅仅会白白消耗系统资源,而像任务管理器这类的程序还会引入多个任务管理器之间的同步问题。所以对些这些程序来说。仅仅有一个实例或程序是必要的。
三、 为什么须要单例模式
上面讲到对于某些程序来说。保持其仅仅有一个实例是必要的,可是怎样保证一个程序或一个类仅仅有一个实例呢?以下从类的角度来讲解。
第一种方法。我们抛开设计模式这个概念,假设你之前全然不知道这个概念。面对这个设计要求你会怎样做?我们能够使用一个全局的类指针变量,初始值为NULL。每当须要创建该类的对象时,都检查该指针是否为NULL。若为NULL,则使用new创建新的对象,并把对象的指针赋值给该全局指针变量。
若该指针不为NULL。则直接返回该指针或使用该指针。
这个可能是最easy想到的方法。
另外一种方法,就是使用单例模式。单例模式通过在类内维护一下指向该类的内部的指针,并把其构造函数声明为private或protected来阻止一般的实例化。而使用一个static的公有成员函数来实现和控制类的实例化。
在该static公有成员函数中推断该类的静态成员指针是否为NULL,若为NULL。则创建一个新的实例。并把该类的静态成员指针指向该实现。若该静态成员指针不为NULL,则直接返回NULL。
若这里看得不是非常明确,不要紧,看了以下的类图和代码自会明确。
相比之下,另外一种方法比第一种方法好在哪里呢?首先。第一种做法并没有强制一个类仅仅能有一个实例,一切的控制权事实上在使用者的设计中。而另外一种做法,则是类的设计者做好的,与使用者并没有关系。
换句话来说,假设使用第一种做法,则仅仅要使用者愿意。该类能够有无数个实例,而对于另外一种方法。不管使用都是否愿意,它仅仅能有一个实例。这就好比我们去吃饭,第一种方法须要顾客来推断哪些菜已经卖完。而另外一种方法由餐馆的推断哪些菜已经卖完,显然在生活中,另外一种方法才是合理的。
四、 单例模式的类图
五、 单例模式的实现(C++实现)
1、singleton.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
2、singleton.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
版权声明:本文博客原创文章。博客,未经同意,不得转载。
辛格尔顿和Android的更多相关文章
-
【原】Android热更新开源项目Tinker源码解析系列之三:so热更新
本系列将从以下三个方面对Tinker进行源码解析: Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Android热更新开源项目Tinker源码解析系列之二:资源文件热更新 A ...
-
配置android sdk 环境
1:下载adnroid sdk安装包 官方下载地址无法打开,没有vpn,使用下面这个地址下载,地址:http://www.android-studio.org/
-
Android SwipeRefreshLayout 下拉刷新——Hi_博客 Android App 开发笔记
以前写下拉刷新 感觉好费劲,要判断ListView是否滚到顶部,还要加载头布局,还要控制 头布局的状态,等等一大堆.感觉麻烦死了.今天学习了SwipeRefreshLayout 的用法,来分享一下,有 ...
-
Android Studio配置 AndroidAnnotations——Hi_博客 Android App 开发笔记
以前用Eclicps 用习惯了现在 想学学 用Android Studio 两天的钻研终于 在我电脑上装了一个Android Studio 并完成了AndroidAnnotations 的配置. An ...
-
Android请求网络共通类——Hi_博客 Android App 开发笔记
今天 ,来分享一下 ,一个博客App的开发过程,以前也没开发过这种类型App 的经验,求大神们轻点喷. 首先我们要创建一个Andriod 项目 因为要从网络请求数据所以我们先来一个请求网络的共通类. ...
-
【原】Android热更新开源项目Tinker源码解析系列之一:Dex热更新
[原]Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Tinker是微信的第一个开源项目,主要用于安卓应用bug的热修复和功能的迭代. Tinker github地址:http ...
-
【原】Android热更新开源项目Tinker源码解析系列之二:资源文件热更新
上一篇文章介绍了Dex文件的热更新流程,本文将会分析Tinker中对资源文件的热更新流程. 同Dex,资源文件的热更新同样包括三个部分:资源补丁生成,资源补丁合成及资源补丁加载. 本系列将从以下三个方 ...
-
Android Studio 多个编译环境配置 多渠道打包 APK输出配置
看完这篇你学到什么: 熟悉gradle的构建配置 熟悉代码构建环境的目录结构,你知道的不仅仅是只有src/main 开发.生成环境等等环境可以任意切换打包 多渠道打包 APK输出文件配置 需求 一般我 ...
-
JS调用Android、Ios原生控件
在上一篇博客中已经和大家聊了,关于JS与Android.Ios原生控件之间相互通信的详细代码实现,今天我们一起聊一下JS调用Android.Ios通信的相同点和不同点,以便帮助我们在进行混合式开发时, ...
随机推荐
-
国外.net学习资源网站
转载 :出处:http://www.cnblogs.com/kingjiong/ 名称:快速入门地址 http://chs.gotdotnet.com/quickstart/ 描述:本站点是微软.NE ...
-
C++ strcpy实现
char * strcpy(char * strDest,const char * strSrc) { if ((NULL==strDest) || (NULL==strSrc)) throw &q ...
-
数据结构——队列(Queues)
队列的存储特性:FIFO(first in first out)即先进先出原则 单向/双向队列 *优先队列(与queue不同) 存储方式: 带尾指针的单向链表 / 数组 queue类: queue() ...
-
linux内核源码目录(转)
Linux用来支持各种体系结构的源代码包含大约4500个C语言程序,存放在270个左右的子目录下,总共大约包含200万行代码,大概占用58MB磁盘空间. 源代码所有在目录:/usr/src/linux ...
-
Ansible hostvars
1. inventory hosts file 中的server 变量会覆盖group变量. hostvars: { "iaas_name": "test", ...
-
[python] 2、python使用pyaudio进行录音,及其在python虚拟环境virtualenv中安装遇到的问题
1.pyaudio安装大背景 最近在做智能音箱,需要编写声音拾取代码,我先是百度两篇比较常见的用python进行录音的操作的文章: python写一个录音小程序:http://blog.csdn.ne ...
-
爬虫小探-Python3 urllib.request获取页面数据
使用Python3 urllib.request中的Requests()和urlopen()方法获取页面源码,并用re正则进行正则匹配查找需要的数据. #forex.py#coding:utf-8 ' ...
-
python 关闭垃圾回收
import gc gc.disable() http://blog.csdn.net/aixiaohei/article/details/6446869
-
Java - equals方法
java提高篇(十三)-----equals()方法总结 equal和==区别 ==比较对象基于内存引用,两个引用完全相同返回true Java 语言里的 equals方法其实是交给开发者去覆写的,让 ...
-
windowsmobile 开发环境
Windows Mobile 6开发环境的配置过程 需要的文件列表:1.Visual Studio 2005 Professional及更高的版本(Visual Studio 2005 Express ...