(一),单例模式
最后更新于:2022-04-01 09:56:57
### 前言
面向对象语言具有封装,继承,多态等三个特性,同时,面向对象语言通常有好多种设计模式,这些设计模式在面向对象语言中是相通的,java是一种面向对象的语言,使用java语言作为开发语言的Android的源码中大量运用了设计模式。单例模式是应用最为广泛而且最为简单的一种设计模式。
单例模式:在任何时刻保证单例对象只存在一个实例,这个实例对象服务于整个系统,如:一个公司只有一个CEO,一个国家只有一个主席或者总统,一个宇宙只有一个地球。同时J2EE中的Application,Session及安卓中的Application都是单例。
### 实现单例模式的几种方式
实现单例模式需要注意一下几个关键点即可:
1. 构造函数私有化,即权限设置为private
1. 通过一个静态方法或者枚举返回单例类对象
1. 确保单例类的实例只有一个,尤其是在多线程的情况下
1. 确保单例类对象在反序列化是不会重新创建对象
通过将单例类的构造函数私有化,使得客户端代码不同通过new产生该类的实例。该单例类通过暴露一个方法给客户端,使得客户端通过该方法得到该单例类的唯一实例,通过还需要在获取该类的实例的过程中保证线程安全,即在多线程的情况下也不能出现单例类多个实例的情况,即单例类的实例在任何情况下都有且只有一个,并向整个系统提供使用。
以java语言为例,实现单例的方式有好多种:
1、懒汉模式(简单但不建议使用)
~~~
public class Singleton {
public static Singleton mInstance;
/**
* 构造函数的私有化
*/
private Singleton (){
}
/**
* 提供一个静态方法获取单例类的实例
* @return
*/
public static synchronized Singleton getInstance(){
if(mInstance == null){
mInstance = new Singleton();
}
return mInstance;
}
}
~~~
这种方式简单,但是每次得到实例对象的时候都要同步,同步的过程是需要开销的,所以效率低,不推荐使用。
2、Double Check Lock(DCL)(一般可以考虑使用) –>改进的懒汉模式
~~~
public class Singleton {
public static Singleton mInstance;
/**
* 构造函数的私有化
*/
private Singleton(){
}
/**
* 提供一个静态方法获取单例类的实例
* @return
*/
public static Singleton getInstance(){
if(mInstance == null){ // 第一次检查
synchronized (Singleton.class) { // lock
if(mInstance == null){ // 第二次检查
mInstance = new Singleton();
}
}
}
return mInstance;
}
}
~~~
这个方式对懒汉模式做了一些改进,synchronized没有加在方法上,而是加在了方法体上。调用该方法的时候先检查实例对象是否为空,不为空直接放返回,为空就加同步(防止多线程出现多个实例的情况),并判断实例对象是否为空,为空就创建。这种方式只会在实例对象第一次为空的情况下同步。克服了懒汉模式每次请求得到实例都有同步,造成不必要的开销的缺点。
注意:该方法由于java内存模型的原因偶尔会失败,但是在绝大多数情况下可以保证单例对象的唯一性。
> java的内存模型,在通过new新建一个实例的时候,这个新建语句最终会编译成多条汇编指令:
(1) 给Singleton的实例分配内存
(2) 调用Singleton()构造函数,初始化成员属性
(3) 将该实例的引用mInstance 指向分配的内存空间
3、静态内部类实现单例(推荐)
~~~
public class Singleton {
/**
* 构造方法私有化
*/
private Singleton(){
}
/**
* 私有的内部静态类,使用了加载外部类的时候内部类不会立即加载的特性
* @author lt
*
*/
private static class SingletonHolder{
public static Singleton mInstance = new Singleton();
}
/**
* 暴露一个方法取得单例对象的实例
* @return
*/
public static Singleton getInstance(){
return SingletonHolder.mInstance;
}
}
~~~
当第一次加载Singleton类的时候,并不会初始化mInstance,只有在调用了Singleton的getInstance()方法时候才会初始化mInstance,第一次调用getInstance()方法会导致虚拟机加载其内部类,这种方式不仅可以确保线程安全,也能够保证单例对象的唯一性,同时也延迟单例对象的初始化。推荐使用
4、枚举单例(超级简单)
~~~
public enum Singleton {
HELLO;
public void println(String text){
System.out.println(text);
}
}
~~~
枚举与java中的普通类一样,不仅可以有字段,也可以有自己的方法,如果我们需要一个对象来实现一个简单的方法能完成的事件,如:Android中显示土司,那么我们就可以考虑使用枚举,并且枚举实例的创建默认是**线程安全**的,其他的类尽管将类的构造方法私有化了,但反序列化是会重新生成实例,而枚举不会。
5、使用容器实现单例模式
~~~
public class Singleton {
private static Map<String,Object> instances = new HashMap<String,Object>();
public static void putObj(String key,Object instance){
if(!instances.containsKey(key)){
instances.put(key, instance);
}
}
public static Object getInstance(String key){
return instances.get(key);
}
}
~~~
该方法就是将已经产生的对象保存到HashMap中暂存起来,每次根据相同的key从这里取到同一个实例。通常在程序开始的时候会将多种类的实例保存到该集合中,这样可以管理这些类的实例,下次直接取出这个实例。
总结:单例模式作为面向对象设计模式中应用最简单最广泛的一种设计模式。这种设计模式带来的好处是:
(1)提高了系统的性能,如:一个包含线程池,缓存系统,网络请求的ImageLoader对象,因为这种类型的对象占用的系统资源大,单例模式解决了这个缺点,整个系统中只有一个该对象的实例,减小了系统性能开销。
(2)减小了系统内存开销,单例模式使得某类对象在内存中只存在一个实例,从而减少了系统内存的占用,
(3)单例模式避免对资源的多重占用保证系统整体的协调,试想一下,如果一个对文件的写操作,如果只有一个对象存在内存中,那么避免了对文件同时写的过程
(4)单例对象可以设置全局的通讯站点,优化和共享资源访问,如:Android中的Application,我们可以在Application中放置一些数据,从而在其他的地方都可以取出该数据,实现数据全局共享。
单例模式的缺点:
(1)单例对象由一般没有接口,也不是抽象的不易扩展,如要扩展,就需要修改类的源代码,这点和开闭原则相违背。
(2)单例对象的使用容易产生一些问题,如:我们在单例对象放置了某些因生命周期或者其它原因我们人为无法控制而失效,那么我们在其它地方使用就会出现内存泄漏等错误。