单例模式是应用最广的模式之一,在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个全局对象,这样有利于协调系统整体行为。
基础知识
定义
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
UML
要素
- 构造函数不对外开放,一般为private.
- 通过一个静态方法或者枚举返回单例类对象
- 确保单例类的对象有且只有一个,尤其是多线程环境下
- 确保单例类对象在反序列化时不会重新构建对象
单例模式的类型
单例设计模式分为:饿汉式、懒汉式、静态内部类、枚举、使用容器实现
饿汉式
public class Singleton{
private static Singleton singleton = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return singleton;
}
}
备注: 饿汉式是线程安全的,因为在调用getInstance()方法之前,该实例就已经创建好了。
懒汉式
懒汉式之原型
public class Singleton {
private static Singleton singleton=null;
private Singleton(){}
public static Singleton getInstance() {
if (singleton== null) {
singleton= new Singleton();
}
return singleton;
}
}
备注: 这种懒汉式是线程不安全的,并发环境下很可能出现多个Singleton实例。
线程不安全的原因:在高并发的情况下,一个线程A执行到singleton = new Singleton(),但还没有获得对象(对象初始化是需要时间的),第二个线程B也在执行,执行到(singleton == null)判断,那么线程B获得判断条件也是为真,于是继续运行下去,线程A获得了一个对象,线程B也获得了一个对象,在内存中就出现两个对象。
线程安全的懒汉式
- 在getInstance方法上加同步
public class Singleton{
private static Singleton instance;
private Singleton(){}
// 为了在多线程情况下保证单例对象唯一性,添加了synchropized关键字
public static synchropized Singleton getInstance(){
if(instance==null){
instance==new Singleton();
}
return instance;
}
}
备注 :单例只有在使用时才会被实例化,在一定程度上节约了资源;第一次加载时需要及时进行实例化,反应稍慢,最大问题是每次调用getInstance都进行同步,造成不必要的同步开销
- 双重检验锁定(DCL)(推荐使用)
public class Singleton{
private volatile static Singleton sinstance=null;
private Singleton(){}
public static Singleton getInstance(){
// 对instance进行了两次判空:第一层判断主要是为了避免不必要的同步,第二层的判断则是为了在null的情况下创建实例
if(sinstance==null){
synchropized(Singleton.class){
if(sinstance==null){
sinstance=new Singleton();
}
}
}
return sinstance;
}
}
备注: 资源利用率高,第一次执行getInstance时单例对象才会被实例化,效率高;第一次加载时反应稍慢,也由于Java内存模式的原因偶尔失败。在高并发环境下也有一定的缺陷,虽然发生概率很小。
静态内部类
public class Singleton{
private Singleton(){}
public static Singleton getInstance(){
return SingletonHolder.sInstance;
}
// 静态内部类
private static class SingletonHolder{
private static final Singleton sInstance=new Singleton();
}
}
备注:当第一次加载Singleton 类时并不会初始化sInstance,只有第一次调用getInstance方法才会导致sInstance被初始化。因此,第一次调用getInstance方法导致虚拟机加SingletonHolder类,这种方式不仅能够线程安全、保证单例对象的唯一性,同时也延迟了单例的实例化。
枚举
public enum SingletonEnum{
INSTANCE;
public void doSomething(){
// 具体操作
}
}
备注: 线程安全
容器实现单例模式
public class SingletonManager{
private static Map<String,Object> objMap=new
HashMap<String,Object>();
pricate SingletonManager() {}
public static void registerService(String key,Object instance){
if(!objMap.containsKey(key)){
objMap.put(key);
}
}
public static Object getService(String key){
return objMap.get(key);
}
}
备注:这种方式可以管理多种类型的单例,并且通过统一的接口进行获取操作,降低了用户的成本,也对用户隐藏了具体实现,降低了耦合度。
优缺点
优点
- 由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地创建、销毁时,而且创建或销毁时性能无法优化,单例模式的优势就非常明显
- 由于单例模式只生成一个实例,所以,减少了系统的性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留内存的方式来解决
- 单例模式可以避免对资源的多重占用,例如一个写文件操作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作
- 单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如,可以设计一个单例类,负责所有数据表的映射处理
缺点
- 单例模式一般没有接口,拓展很困难,若要拓展,除了修改代码基本上没有第二种途径可以实现
- 单例对象如果持有Context,那么很容易引发内存泄漏,此时需要注意传递给单例对象的Context最好是Application Context
适用场景
确保某个类有且只有一个对象的场景,避免产生多个对象消耗过多的资源,或者某种类型的对象只应该有且只有一个。例如:创建一个对象需要消耗的资源过多,如要访问IO和数据库等资源。
参考
- Android源码设计模式解析与实战
- 设计模式之禅