辛格尔顿(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的更多相关文章

  1. 【原】Android热更新开源项目Tinker源码解析系列之三:so热更新

    本系列将从以下三个方面对Tinker进行源码解析: Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Android热更新开源项目Tinker源码解析系列之二:资源文件热更新 A ...

  2. 配置android sdk 环境

    1:下载adnroid sdk安装包 官方下载地址无法打开,没有vpn,使用下面这个地址下载,地址:http://www.android-studio.org/

  3. Android SwipeRefreshLayout 下拉刷新——Hi_博客 Android App 开发笔记

    以前写下拉刷新 感觉好费劲,要判断ListView是否滚到顶部,还要加载头布局,还要控制 头布局的状态,等等一大堆.感觉麻烦死了.今天学习了SwipeRefreshLayout 的用法,来分享一下,有 ...

  4. Android Studio配置 AndroidAnnotations——Hi_博客 Android App 开发笔记

    以前用Eclicps 用习惯了现在 想学学 用Android Studio 两天的钻研终于 在我电脑上装了一个Android Studio 并完成了AndroidAnnotations 的配置. An ...

  5. Android请求网络共通类——Hi_博客 Android App 开发笔记

    今天 ,来分享一下 ,一个博客App的开发过程,以前也没开发过这种类型App 的经验,求大神们轻点喷. 首先我们要创建一个Andriod 项目 因为要从网络请求数据所以我们先来一个请求网络的共通类. ...

  6. 【原】Android热更新开源项目Tinker源码解析系列之一:Dex热更新

    [原]Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Tinker是微信的第一个开源项目,主要用于安卓应用bug的热修复和功能的迭代. Tinker github地址:http ...

  7. 【原】Android热更新开源项目Tinker源码解析系列之二:资源文件热更新

    上一篇文章介绍了Dex文件的热更新流程,本文将会分析Tinker中对资源文件的热更新流程. 同Dex,资源文件的热更新同样包括三个部分:资源补丁生成,资源补丁合成及资源补丁加载. 本系列将从以下三个方 ...

  8. Android Studio 多个编译环境配置 多渠道打包 APK输出配置

    看完这篇你学到什么: 熟悉gradle的构建配置 熟悉代码构建环境的目录结构,你知道的不仅仅是只有src/main 开发.生成环境等等环境可以任意切换打包 多渠道打包 APK输出文件配置 需求 一般我 ...

  9. JS调用Android、Ios原生控件

    在上一篇博客中已经和大家聊了,关于JS与Android.Ios原生控件之间相互通信的详细代码实现,今天我们一起聊一下JS调用Android.Ios通信的相同点和不同点,以便帮助我们在进行混合式开发时, ...

随机推荐

  1. javascripte (三) 改变html图像

    <script> function changeImage(){ element=document.getElementById("myimage") if (elem ...

  2. linux时间方面的设置

    例如以下一段代码能够借鉴: static void _sleep_response_timeout(modbus_t *ctx) { #ifdef _WIN32 /* usleep doesn't e ...

  3. HDU 1535 Invitation Cards (POJ 1511)

    两次SPFA. 求 来 和 回 的最短路之和. 用Dijkstra+邻接矩阵确实好写+方便交换.可是这个有1000000个点.矩阵开不了. d1[]为 1~N 的最短路. 将全部边的 邻点 交换. d ...

  4. WSDL中文版——详解

    为什么使用WSDL? 像Internet协议之类的标准有没有为权威所利用,或者人们这样看待它是因为顺之所获的好处远远超出了代价?曾经有许多试图建立的标准都流产了.有时候,那些还没有普遍使用的标准甚至由 ...

  5. Oracle的dbms_output包的put()和put_line()的区别只是有没有回车换行吗?(转)

    答案是否 除了自动添加回车换行外,还有就是缓冲区最大容量的问题!! 无论如何设置serveroutput size,10g里 put() 最多只能输出 32767 个byte 而 put_line() ...

  6. SVNKit getFileFromSVN

    /* * ==================================================================== * Copyright (c) 2004-2011 ...

  7. 14.4.2 Configuring InnoDB for Read-Only Operation 配置InnoDB 永于只读操作:

    14.4.2 Configuring InnoDB for Read-Only Operation 配置InnoDB 永于只读操作: 你可以查询InnoDB 表 MySQL 数据目录是在只读介质里,通 ...

  8. Linux正则表达式grep与egrep

    grep -io "http:/=[A-Z0-9]\{16\}" ./wsxf.txt >wsxf_urls.txt Linux正则表达式grep与egrep 正则表达式:它 ...

  9. java matlab混合编程之返回值Struct类型

    java matlab混合编程的时候当返回值是Struct类型(matlab中的返回类型)如何来取得(java中)其值? 上网找,看到这个网页:http://www.mathworks.cn/cn/h ...

  10. Mysql 导入导出数据结构及数据

    方式一: mysqldump -ukevin -P3306 --default-character-set=utf8 -p -h10.1.15.123 activity sign_in_user &g ...