1. 概述
单例模式是最简单的一种设计模式,先来看看它的定义:
Ensure a class has only one instance, and provide a global point access of it
确保一个类只有实例,并且自动实例化改实例,并且提供一个全局的接口能够获取这个唯一的实例。
- 减少内存的开支,特别是一个对象需要频繁创建和销货时。并且当一个类在内存中只存在一个实例时减少系统内存的开销。
- 单例模式可以避免对资源的多重占用,比如写一个文件,可以避免对同一个文件进行写操作。
2. 简单示例
单例模式虽然简单,但是也有许多的坑,我们从最简单的单例模式说起:
//饿汉模式
class Singleton{
private final Singleton instance = new Singleton();
//将默认的构造函数私有化,防止通过其他的方式new出改类的实例
private Singleton(){
}
public static Singleton getInstance(){
return instatnce;
}
}
- 饿汉模式:避免了多线程的同步问题,节省了运行时的判断(运行时间)。但是无论调不调用
getInstance()
都会在加载类的时候初始化实例,浪费了内存。
//懒汉模式
class Singleton{
private final Singleton instance;
//将默认的构造函数私有化,防止通过其他的方式new出改类的实例
private Singleton(){
}
public synchronized static Singleton getInstance(){
if(instance == null)
instance = new Singleton();
return instatnce;
}
}
- 懒汉模式:达到了懒加载,只有在使用的时候才会初始化,并且第一次加载的时间稍长。并且为了避免多线程操作出现同步问题,加入了
synchronized
字段,这就造成了每次获取实例的时候都会在线程同步上浪费时间,造成不必要的开销。这种模式一般不建议采用。
3.单例模式的多种实现
3.1 Double Check Lock(DCL) 实现单例
DCL方式实现单例模式的有点在于既能够在需要时进行初始化,又能保证线程安全,且单例对象在初始化后调用getInstance不进行同步锁,代码如下:
public class Singleton {
private volatile static Singleton sInstance = null;
private Singleton(){
}
public static Singleton getsInstance(){
//为了避免不必要的同步,因为只有在未初始化时才需要进行同步
if(sInstance == null){
synchronized (Singleton.class){
if(sInstance == null){
sInstance = new Singleton();
}
}
}
return sInstance;
}
}
为什么要判断两次sInstance == null
?
因为
sInstance = new Singleton();
这条语句不是原子的。这条语句会被最终汇编为以下几条指令:
1)改sInstance分配内存
2)初始化sInance字段
3)将sInstance对象指向分配的内存(此时sInstance才不为null)
在JDK1.5及以前上面2.3的执行顺序是不能保证的,因此可能在1-3-2时,对象还未处理好就被另一个线程给取走了,使用时就会出现问题,此时采用DLC检查就失效了。
在JDK1.5以后,java调整了volatile关键字,与C语言中的volatile定义相同,表明变量可能在使用时被意想不到的改变,此时优化器在用到这个值时会小心的读取这个变量的值(即逐一的运行会变代码)。因此在jdk1.6及以后只要在变量前加入volatile关键字就能保证dlc是有效的。
虽然volatile关键字会影响一部分性能,但能换取来程序的稳定,牺牲这点性能还是值得的。
3.2 静态内部类单例模式
public class Singleton {
private Singleton() {
}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
/**
* 静态内部类
*/
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
}
- 使用内部类。只有被外部类调用时才会创建实例,同时不用加锁避免了线程同步问题,结合了饿汉、懒汉、DLC的优点,是最佳的单例模式的实现方式 。
3.3 枚举单例
枚举在java中与普通类是一样的,不仅能够有字段,还能有自己的方法,最重要的是默认是线程安全的,并且在任何情况下都是一个单例。
最终要的是上述几种模式,在一个情况下会出现重复创建对象的情况,就是反序列化。反序列化提供了一个很特别的HOOK函数,readResolve()
;如果要杜绝反序列化的可以在类中添加该方法。
private Object readResolve() throws ObjectStreamException{
return sInstance;
}
使用枚举单例:
public enum Singleton{
//定义的枚举常量,也就是静态类实例
INSTANCE;
//下面添加提供的各种成员及静态方法
}
3.4 使用容器实现单例模式
在程序的开始讲多种单例类型注入到统一的管理类中,在使用时根据key获取对应的类型的对象,这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的即可进行获取操作,降低了用户的使用成本,也对用户隐藏了具体的实现,降低了耦合度
public class SingletonManager {
private static Map<String, Object> objMap = new HashMap<String,Object>();
private Singleton() { }
public static void registerService(String key, Objectinstance) {
if (!objMap.containsKey(key) ) {
objMap.put(key, instance) ;
}
}
public static ObjectgetService(String key) {
return objMap.get(key) ;
}
}
4. android系统源码中所应用的单例模式
在android系统中源码中使用的单例模式,多是采用容器实现单例模式,在静态代码块中完成注册。如LayoutInflater、getSystemService等。
最后:在android中,静态实例持有contex时容易造成内存泄漏,此时注意传递给静态实例的contex最好是application的contex。