目录
- 定义
- 使用场景
- UML类图
- 实现方式
- 饿汉式
- 懒汉式
- Double Check LockDCL双重检查锁
- 静态内部类
- 枚举单例
- 使用容器实现单例
- Android源码中的单例模式
- 总结
- 优点
- 缺点
博客地址
最近在看《Android源码设计模式解析与实战》这本书,发现里面还有对源码的一些分析,之前也没好好看过设计模式,就来做个笔记,跟着看一下。
包括设计模式和一些源码分析。
定义
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
使用场景
需要确保一个类只有一个实例的场景,避免产生多个对象小号过多的资源,或者是这个类只应该有一个实例。比如创建一个对象要消耗的资源过多,或者要访问IO和数据库等资源.
配置文件,工具类,线程池,缓存,日志对象等。
UML类图
角色介绍:
. Client——高层客户端
. Singleton——单例类
实现单例模式的关键点:
- 构造函数不对外开放,一般为Private。就是不允许外部通过new Singleton()来获取对象。
- 通过一个静态方法或枚举返回单例类对象,如getInstance()方法。
- 确保单例类的对象只有一个,尤其是在多线程的情况下。确保即使在多线程也能实现单例。
- 确保单例类对象在反序列化时不会重新构建对象。
实现方式
饿汉式
public class Singleton {
private static final Singleton mSingleton = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return mSingleton;
}
}
里面的对象是个静态对象,第一次声明的时候就会实现初始化。外部只能通过getInstance()获取到这个对象,实现单例。
懒汉式
public class Singleton {
private static Singleton mSingleton ;
private Singleton(){}
public static synchronized Singleton getInstance(){
if (mSingleton==null){
mSingleton=new Singleton();
}
return mSingleton;
}
}
这里的getInstance()方法加上了synchronized关键字,保证了多线程也能实现单例。
优点:
单例只有在使用时才会被实例化,一定程度上节约了资源。
缺点:
(1)单例在第一次加载时要及时进行实例化,反应稍慢
(2)每次调用getInstance()都要进行同步,造成不必要的同步开销。
所以一般不建议用。
Double Check Lock(DCL)双重检查锁
public class Singleton {
private static Singleton mSingleton ;
private Singleton(){}
public static Singleton getInstance(){
if (mSingleton==null){
synchronized (Singleton.class){
if (mSingleton==null){
mSingleton=new Singleton();
}
}
}
return mSingleton;
}
}
这个getInstance方法中对mSingleton进行了两次判空:第一次是为了避免不必要的同步锁;第二层是为了在null的时候创建实例。
DCL失效:
在多线程下,假设A线程执行到mSingleton=new Singleton()的时候,CPU并不是一次性执行完这条语句的,因为这不是一个原子操作(指不会被线程调度机制打断的操作)。
举个例子:执行 Timer timer = new Timer(); 通过字节码文件可以看到这一行代码编译出来是这样的:
0: new #2 // class java/util/Timer
3: dup
4: invokespecial #3 // Method java/util/Timer."<init>":()V
7: astore_1
8: return
所以mSingleton=new Singleton()大致做了三件事:
(1)给Singleton的实例分配内存
(2)调用Singleton的构造方法
(3)将mSingleton指向分配的内存空间(这个时候mSingleton才不为空)
由于Java编译器允许处理器乱序执行,所以上面的第二步第三步的执行顺序没法得到保证。执行顺序可能是1-2-3也可能是1-3-2。
当A线程执行顺序是1-3-2的时候,如果执行到了1-3,第2步还没执行的时候,如果B线程判断mSingleton==null的时候就会的发哦FALSE的结果,从而返回一个错误的单例。
优点:
资源利用率高,第一次执行getInstance的时候才会被实例化,效率高。
缺点:
第一册加载反应稍慢,而且有失败的可能,但是概率很小。
这种是用的最多的单例实现方式,大部分情况下都能保证单例。
静态内部类
public class Singleton {
private Singleton(){}
public static Singleton getInstance(){
return SingletonHolder.mSingleton;
}
private static class SingletonHolder{
private static final Singleton mSingleton = new Singleton();
}
}
当第一次加载Singleton的时候并不会初始化mSingleton,只有在第一次调用getInstance的时候才会加载SIngletonHolder类。
优点:
不仅能保证线程安全,也能保证单例的唯一性,也延迟了单例的实例化,比较推荐。
枚举单例
public enum Singleton{
INSTANCE;
public void doThing(){
System.out.println(this.hashCode());
}
}
使用时可以通过Singleton singleton = Singleton.INSTANCE;
来获取单例。
优点:
写法简单,而且默认线程安全,任何情况下都是一个单例。
特点:
上面的几种在有一种情况下会单例失效,出现重复创建对象,那就是反序列化。
反序列化的时候会调用一个readResolve()方法重新生成一个实例,所以上面的几种方式要解决这个问题需要加入以下方法,:
public class Singleton {
private Singleton(){}
public static Singleton getInstance(){
return SingletonHolder.mSingleton;
}
private static class SingletonHolder{
private static final Singleton mSingleton = new Singleton();
}
private Object readRedolve() throws ObjectStreamException{
return SingletonHolder.mSingleton;
}
}
使用容器实现单例
public class SingletonManager {
private static Map<String,Objects> objMap = new HashMap<>();
private SingletonManager(){}
public static void registerService(String key,Object obj){
if (!objMap.containsKey(key)){
objMap.put(key,obj);
}
}
public static Object getService(String key){
return objMap.get(key);
}
}
在程序的开始,将许多要单例的对象放到一个容器里,用的时候根据key取得对应的单例对象。
Android源码中的单例模式
源码中的单例模式太多了,甚至有一个专门的单例的抽象类:
package android.util;
public abstract class Singleton<T> {
private T mInstance;
protected abstract T create();
public final T get() {
synchronized (this) {
if (mInstance == null) {
mInstance = create();
}
return mInstance;
}
}
}
我们经常通过context.getSystemService(String name)来获取一些系统服务,如在Activity中获取ActivityManager:
ActivityManager mActivityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
书中举例为LayoutInflater,平时获取方式为LayoutInflater.from(context)
,看下这个方法:
package android.view;
public static LayoutInflater from(Context context) {
LayoutInflater LayoutInflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (LayoutInflater == null) {
throw new AssertionError("LayoutInflater not found.");
}
return LayoutInflater;
}
发现也是通过调用context.getSystemService(String name)获取的。
那么扎个单例是怎么实现的呢?顺着代码往上看吧。。
context.getSystemService(String name)直接点进去的话会进到
package android.content;
public abstract class Context {
...
public abstract Object getSystemService(@ServiceName @NonNull String name);
...
}
通过分析activity的启动流程可以知道,Context的功能的具体实现是在ContextImpl.java中,看具体实现代码:
package android.app;
class ContextImpl extends Context {
...
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
}
...
然后继续SystemServiceRegistry.getSystemService(this, name):
package android.app;
final class SystemServiceRegistry {
...
//用来getSystemService的容器,里面存放的是ServiceFetcher<?>
private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
new HashMap<String, ServiceFetcher<?>>();
...
//静态代码块,第一次加载时执行,而且只会执行一次,保证了注册的服务的唯一性。
static {
registerService(Context.ACCESSIBILITY_SERVICE, AccessibilityManager.class,
new CachedServiceFetcher<AccessibilityManager>() {
@Override
public AccessibilityManager createService(ContextImpl ctx) {
return AccessibilityManager.getInstance(ctx);
}});
registerService(Context.DOWNLOAD_SERVICE, DownloadManager.class,
new CachedServiceFetcher<DownloadManager>() {
@Override
public DownloadManager createService(ContextImpl ctx) {
return new DownloadManager(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);
}
...
public static Object getSystemService(ContextImpl ctx, String name) {
ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}
...
}
里面还不是直接拿到服务,而是调用了fetcher.getService(ctx)来获取服务。看看ServiceFetcher<?>:
static abstract interface ServiceFetcher<T> {
T getService(ContextImpl ctx);
}
这是个接口,看上面的静态代码块里面的方法发现注册服务的时候都是用的CachedServiceFetcher这个类:
static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> {
private final int mCacheIndex;
public CachedServiceFetcher() {
mCacheIndex = sServiceCacheSize++;
}
@Override
@SuppressWarnings("unchecked")
public final T getService(ContextImpl ctx) {
//ctx.mServiceCache是获取一个数组:new Object[sServiceCacheSize];
//数组的长度就是构造方法中的那个变量,每注册一个服务,就会new一个对应的CachedServiceFetcher,然后数组长度就+1。第一次获取到这个数组肯定是个空数组
final Object[] cache = ctx.mServiceCache;
synchronized (cache) {
// Fetch or create the service.
Object service = cache[mCacheIndex];
//第一次获取这个服务的时候,数组是空的 ,所以service == null为TRUE。
if (service == null) {
//调用注册时实现的createService方法,把生成的具体服务放在数组对应下标中,
//之后就直接从数组中获取了。实现了单例。
service = createService(ctx);
cache[mCacheIndex] = service;
}
return (T)service;
}
}
// 在静态代码块中实现
public abstract T createService(ContextImpl ctx);
}
里面有个抽象方法,需要实例化的时候实现。在静态代码块中的方法都实现了这个createService(ContextImpl ctx)方法,并且返回了对应的服务。
附上部分注册服务的截图,截不下:
总结
优点:
- 由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁创建,或者创建或销毁时性能无法优化,单例模式的优势就很明显了。
- 由于只生成一个实例,减少了系统性能开销。
- 可以避免对资源的多重占用,如文件操作等。
- 单例模式可以设置为全局的访问点,优化和共享资源访问。
缺点
- 单例模式一般没有接口,拓展很困难,基本都要修改源代码。
- 在Android中,如果单例模式持有Activity的Context,容易产生内存泄漏。所以尽量用ApplicationContext。