辛格尔顿和Android

辛格尔顿(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

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

时间: 2025-01-16 17:29:34

辛格尔顿和Android的相关文章

android开发之onCreate( )方法详解

onCreate( )方法是android应用程序中最常见的方法之一,那么,我们在使用onCreate()方法的时候应该注意哪些问题呢? 先看看Google Android Developers官网上的解释: onCreate(Bundle) is where you initialize your activity. Most importantly, here you will usually call setContentView(int) with a layout resource d

如何用 Android Studio 导入开源项目以及常见错误的解决办法

声明:这篇文章完全来自这篇文章,感谢大神的相助.这篇文章纯粹是为了备份. 本篇以Github上最热门的MaterialDesign库,MaterialDesignLibrary来介绍怎样使用Android Sudio导入开源项目的,如果你和我一样刚刚从Eclipse转到AS,那本篇文章非常适合你. 如果不引入任何第三方库,不做自动化分渠道打包等操作,那可以在完全不了解Gradle的情况下进行Android项目的开发.但如果要想导入Github上的热门项目,必须首先熟悉Gradle. 1. Gra

Android——调用高德地图API前期准备

1.登陆高德开放平台注册账号http://lbs.amap.com/ 2.创建自己的应用并且添加新key 获取发布版安全码获取方法: 在AndroidStudio的Terminal中编译: 输入如下图: 开发版安全码获取: 开发版获取的另一种方法:(和获取发布版本一样在AndroidStudio的Terminal中编译 只是keystore的路径改成debug.keystore    密钥库口令默认是android) 创建key成功 得到key: 前期准备完成 下一篇文章是开发配置 备忘 ,也希

Android小例子:使用反射机制来读取图片制作一个图片浏览器

效果图: 工程文件夹: 该例子可供于新手参考练习,如果有哪里不对的地方,望指正>-< <黑幕下的人> java代码(MainActivity.java): package com.example.imageswitchtest; import java.lang.reflect.Field; import android.app.Activity; import android.os.Bundle; import android.util.Log; import android.v

Android Studio 安装及常见问题

今年放假比去年早了一些,就提前回来了.感觉挺爽,结果教研室电脑没有带回来,悲剧是导师让我维护一下以前的项目,只能屁颠屁颠的搞起呀.只能用自己的笔记本搭建android开发环境.由于前阶段听说Android Studio用着爽歪歪,于是尝试在超卡的笔记本上跑Android Studio.哇咔咔,费了九牛二虎之力终于将Android Studio玩转了.先上个图,快过年了,祝大家新年快乐! Android官网也开始推广Android Studio了,以前官网好像提供Eclipse和ADT打包下载,现

Android 导航条效果实现(六) TabLayout+ViewPager+Fragment

TabLayout 一.继承结构 public class TabLayout extends HorizontalScrollView java.lang.Object ? android.view.View ? android.view.ViewGroup ? android.widget.FrameLayout ? android.widget.HorizontalScrollView ? android.support.design.widget.TabLayout 二.TabLayou

Android Studio 连接真机不识别

本人也是初学..写错的请大神多多批评指正! 不胜荣幸!! 强烈推荐使用真机测试..除非是最后关头要测试各个Android系统版本.. 本人遇到的连不上的原因有以下几种: 1  --   手机设置问题.开USB调试   方法:  手机设置 - 开发人员选项 - USB调试  - 勾选 2  --   数据线问题.  有的数据线只能用来充电,有的可以连接存储.识别方法很简单..插上机器有USB存储设备的提示的就可以用.另外数据线如果都露线皮了..就赶紧扔了.十块八块的总比你为这个破问题纠结一下午的好

android Activity 的生命周期 以及横屏竖屏切换时 Activity 的状态变化

生命周期Android 系统在Activity 生命周期中加入一些钩子,我们可以在这些系统预留的钩子中做一些事情.例举了 7 个常用的钩子:protected void onCreate(Bundle savedInstanceState)protected void onStart()protected void onResume()protected void onPause()protected void onStop()protected void onRestart()protecte

谈谈-Android中的接口回调技术

Android中的接口回调技术有很多应用的场景,最常见的:Activity(人机交互的端口)的UI界面中定义了Button,点击该Button时,执行某个逻辑. 下面参见上述执行的模型,讲述James对Android接口回调技术的理解(结合前人的知识和自己的实践). 使用一个比喻很形象地说明:客户端有个疑问打电话请教服务端,但服务端无法现场给出解答,相互之间约定:服务端一旦有答案,使用电话的方式反馈给客户端. 以上有三个主体:客户端.服务端和接口(方式). 接口回调的原理框图说明: Demo界面