设计模式(一)单例模式及WMS中的单例

1,125 阅读6分钟

一、基本概念

1、单例基本概念

定义:

  • 确保某个类有且只有一个实例对象

优点:

  • 内存中只有一个实例,可减少内存使用,尤其是需要频繁创建、销毁对象的场景,单例模式比较有优势。
  • 避免对资源的多重占用,比如读配置、写文件等操作,只有一个对象去操作资源,避免了多个内存对象对资源的同时操作。
  • 单例可设置全局的访问点,共享资源访问。

缺点:

  • 扩展比较困难,一般单例没有接口
  • 单例模式跟单一职责原则冲突,单例会将多种业务逻辑放在一个类中。
  • 单例对象如果持有Context,容易引起内存泄露,最好传入ApplicationContext

使用场景:

  • 项目中需要一个共享访问点或共享数据,例如全局配置文件、数据库管理类、网络请求管理类等。
  • 创建对象需要消耗资源比较多,例如访问IO、数据库等资源时。

关键点:

  • 构造函数不对外开发
  • 确保多线程环境下对象只有一个

2、非线程安全的单例:

public class Singleton {
	private static Singleton singleton = null;
	
	private Singleton(){

	}

	public static Singleton getInstance(){
		if(singleton == null) {
			singleton = new Singleton();
		}
		return singleton;
	}
}

如果两个线程同时执行到singleton == null 的判断的时候,两个线程条件都满足,会出现创建两个对象的情况,违反了单例只创建一个对象的原则。

二、线程安全的单例

1、饿汉式

public class Singleton {
    private static Singleton singleton = new Singleton();

    private Singleton() {

    }

    public static Singleton getInstance() {
        return singleton;
    }
}
  • 实现原理:通过static修饰Singleton,静态变量随着类加载时完成初始化,在内存中只会有一个,所有没有线程安全的问题。
  • 缺点:先将对象创建出来,并没有实际的调用,会造成不必要的内存消耗。

2、懒汉式(同步方法)

public class Singleton {
    private static Singleton singleton = null;
    private Singleton(){

    }
    public synchronized static Singleton getInstance(){
        if(singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}
  • 实现原理:getInstance通过synchronized关键字进行修饰,保证只有一个线程执行getInstance方法内的创建逻辑,保证创建出来的对象只有一个。
  • 优点:用的时候才进行创建,减少不必要的内存消耗。
  • 缺点:synchronized关键字存在效率问题,线程A执行到getInstance方法时,线程B只能处于等待状态,只有等线程A执行完getInstance方法,线程B才能继续执行。

3、懒汉式(双重检查加锁)

public class Singleton {
    private static volatile Singleton singleton = null;

    private Singleton() {

    }

    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

双层判断的作用

  • 外层singleton == null 判断为true,才执行synchronized所修饰的内部逻辑,否则直接获取singleton对象,保证需要有对象创建就不需要等待。
  • 内层singleton == null 判断是为了再次检查对象是否创建。

为什么
线程A执行到synchronized修饰的代码时,此时线程B正在执行内部逻辑,所以线程A只能处于等待状态。线程B执行完毕并将对象创建出来了,而线程A是不知道的,会再次创建,所以需要再次判断对象是否为空。

volatile的作用
singleton = new Singleton() 不是原子操作,这段代码会编译成多条指令

  1. 将对象new出来,给Singleton分配内存空间
  2. 调用Singleton构造函数,初始化成员变量
  3. 将singleton指向分配的内存,此时singleton就不为空了
    java编译器允许指令乱序执行,所以2、3步执行顺序无法保证,就可能线程B执行了这段代码,singleton指向了内存空间,但是成员变量还没初始化完。此时如果线程A通过singleton== null进行判断,发现对象不为空,拿对象去使用,但是成员变量还没初始化完,就会出错。

被volatile关键字修饰,能够保证内存可见性和禁止进行指令重排序,通过该特性保证了singleton = new Singleton()的指令执行顺序

  • 实现原理:通过synchronized和volatile实现线程安全。
  • 优点:缩小了synchronized关键字所修饰的范围
  • 缺点:volatile屏蔽掉了编译器必要的代码优化,所以在效率上会有影响

4、静态内部类

public class Singleton {
    private Singleton() {

    }

    private static class SingletonInner {
        private static final Singleton sInstance = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonInner.sInstance;
    }
}

实现原理:

1.如何保证线程安全:利用了Classloader的机制保证初始化时只有一个线程。虚拟机会保证静态类初始化的方法在多线程环境被正确加锁、同步,如果多个线程去初始化,只会有一个线程(线程A)去执行初始化操作,其他线程(线程B)需要阻塞等待。线程A执行完毕后,线程B唤醒后不会进入初始化的方法。同一个类加载器下,只会初始化一次
2.是否为延迟加载:内部类在被调用时才会被加载, 虽然Singleton被加载了,但是内部类不需要立即加载,所以Singleton还没有实例化,也算是懒汉式的一种(延迟加载),只有主动调用getInstance方法时Singleton才会被实例化。

优点:延迟加载,并且没有线程同步问题,效率更高。

5、枚举

public enum Singleton{
    SINGLETON;
}
  • 实现原理:对枚举进行反编译后,SINGLETON被声明为static的,虚拟机保证一个静态变量的线程安全问题,所以枚举是线程安全的。
  • 优点:《Effective Java》中提到,功能完整、使用简洁、无偿地提供了序列化机制、在面对复杂的序列化或者反射攻击时仍然可以绝对防止多次实例化等优点
    而其他单例方式如果禁止在反序列化时重新创建对象,需要在readResolve方法中,将sInstance返回。
private Object readResolve() throws ObjectStreamException {
    return sInstance;
}

6、kotlin中的object

kotlin可以直接通过object实现单例对象

object ObjPerson {
  private val name : String = "hhh"

  fun getName() : String {
    return name
  }
}

如何实现的?反编译java代码如下,类似java单例中的饿汉模式,静态变量会随着类加载时完成初始化,保证了在内存中的唯一性

public final class ObjPerson {
  public static final ObjPerson INSTANCE = new ObjPerson();
  private static final String name = name;

  private ObjPerson() {
  }

  public final String getName() {
    return name;
  }
}

三、WindowManagerService的单例

AMS、PMS、WMS等核心类如何实现的单例?

1、获取WMS

context.getSystemService(Context.WINDOW_SERVICE);

实现调用了ContextImpl中的方法

@Override
public Object getSystemService(String name) {
  return SystemServiceRegistry.getSystemService(this, name);

}

发现是在map中取数据,

final class SystemServiceRegistry {

  private static final Map<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
      new ArrayMap<String, ServiceFetcher<?>>();
......

  public static Object getSystemService(ContextImpl ctx, String name) {
    ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
    return fetcher != null ? fetcher.getService(ctx) : null;
  }
}

2、存储AMS

final class SystemServiceRegistry {
  static {
    //CHECKSTYLE:OFF IndentationCheck
    registerService(Context.ACCESSIBILITY_SERVICE, AccessibilityManager.class,
        new CachedServiceFetcher<AccessibilityManager>() {
          @Override
          public AccessibilityManager createService(ContextImpl ctx) {
            return AccessibilityManager.getInstance(ctx);
          }
        });
  }

......
  private static <T> void registerService(String serviceName, Class<T> serviceClass,
      ServiceFetcher<T> serviceFetcher) {
    SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
    SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
  }
}

静态代码块,首次加载该类时执行,并且只执行一次,保证了对象的唯一

汇总: 设计模式总结

参考资料:

  • 《设计模式之禅》
  • 《Android源码设计模式解析与实战》