单例模式
定义:
确保一个类中只有一个实例,并且自行实例化向整个系统提供这个实例
使用场景
适用于所创建的对象过于消耗资源,如访问IO、数据库等资源时,可使用单例模式,确保该类只有一个实例。如ImageLoader,其中包含线程池、缓存系统、网络请求等,很消耗资源,这种不能自由构造对象的情况,就是单例模式的使用场景
单例模式的写法
1、饿汉模式
public class SingleTask {
//创建实例
private static SingleTask singleTask = new SingleTask();
private SingleTask(){}
public static SingleTask getInstance(){
return singleTask;
}
}
饿汉模式是线程安全的,并且在类加载的时候进行初始化,所以在类加载过程中相对较慢的,但是在使用的比较快
2、懒汉模式
public class SingleTask {
private static SingleTask singleTask;
private SingleTask(){}
public static SingleTask getInstance(){
if (singleTask == null) {
//判断为null,才创建
singleTask = new SingleTask();
}
return singleTask;
}
}
懒汉模式声明静态对象,并在使用的时候才初始化,节约资源,但是第一次调用的时候需要初始化比较消耗时间,并且这种写法在多线程中是不安全的,由此可引申第三种写法
3、线程安全型懒汉模式
public class SingleTask {
private static SingleTask singleTask;
private SingleTask(){}
//加上关键字synchronized,保证线程安全
public static synchronized SingleTask getInstance(){
if (singleTask == null) {
singleTask = new SingleTask();
}
return singleTask;
}
}
这种写法能够在多线程中很好的工作,但是每次调用getInstance方法时都需要进行同步,造成资源的浪费和性能上的一些影响,同时,这种同步在大部分情况下是不会用到的,所以不建议使用这种方式
4、双重检查模式
public class SingleTask {
//使用volatile 关键字保证双重检查模式的正确性
private volatile static SingleTask singleTask = null;
private SingleTask(){}
public static SingleTask getInstance(){
if (singleTask == null) {
//双重检查
synchronized (SingleTask.class) {
if (singleTask == null) {
singleTask = new SingleTask();
}
}
}
return singleTask;
}
}
这种写法在getInstance方法中对singleTask 进行了两次判空,第一次是为了不必要的同步,第二次是在singleton等于null的情况下才创建实例
5、静态内部类单例模式
public class SingleTask {
private SingleTask(){}
public static SingleTask getInstance() {
return SingleTaskHolder.sInstance;
}
//静态内部类
private static class SingleTaskHolder {
private static final SingleTask sInstance = new SingleTask();
}
}
双重检查模式实现的单例在大部分的情况下都是可以正确使用的,但是在比较复杂的情况下会偶尔失效,所以推荐使用静态内部类单例模式,第一次加载SingleTask 类时并不会初始化sInstance,在调用getInstance( )方法是才会加载SingleTaskHolder 类,进而初始化sInstance,这种方法既能保证线程的安全,也能保证单例对象的唯一想,同时也延迟了单例的实例化,所以是推荐使用这种单例实现方法
6、枚举单例
public enum SingleTask{
/**
* 定义一个枚举的元素,其代表的是SingleTask的一个实例
*/
INSTANCE;
/**
* 单例自己的操作
*/
public void operation() {
//相关功能
}
}
枚举实例的创建是线程安全的,并且在任何情况下都是单例,上述讲的几种单例模式实现中,有一种情况下他们会重新创建对象,那就是反序列化,将一个单例实例对象写到磁盘再读回来,从而获得了一个实例。反序列化操作提供了readResolve方法,这个方法可以让开发人员控制对象的反序列化
7、使用容器实现单例模式
public class SingleTaskManager {
private static Map<String,Object> objMap = new HashMap<>();
private SingleTaskManager() {}
//将多种单例类型统一到一个管理器中
public static void registerService(String key,Object instance) {
if ((!objMap.containsKey(key))) {
objMap.put(key,instance);
}
}
//根据key来获取对应的对象
public static Object getService(String key) {
return objMap.get(key);
}
}
这种写法,可以通过统一的接口获取操作,降低使用成本,隐藏具体实现,降低了耦合度
源码中的单例模式Context
我们先来看看Android中Context的继承结构图
图片来源:https://blog.csdn.net/qinjuning/article/details/7310620
由于ContextImpl是保护文件,所以在IDE中是看不到的,可以使用Source Insight查看源码
- ContextIml : Context的子类,是真正实现了Context的所有方法
- ContextWrapper: Context的包装类,所以ContextWrapper构造函数中必须包含一个真正的Context引用,同时ContextWrapper中提供了attachBaseContext()用于给ContextWrapper对象中指定真正的Context对象,调用ContextWrapper的方法都会被转向其所包含的真正的Context对象
- ContextThemeWrapper : 其内部包含了与主题(Theme)相关的接口,这里所说的主题就是指在AndroidManifest.xml中通过android:theme为Application元素或者Activity元素指定的主题
public class ContextWrapper extends Context {
Context mBase;
public ContextWrapper(Context base) {
mBase = base;
}
protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException("Base context already set");
}
mBase = base;
}
.......
}
简而言之,Context的两个子类分工明确,其中ContextImpl是Context的具体实现类,ContextWrapper是Context的包装类。Activity,Application,Service虽都继承自ContextWrapper(Activity继承自ContextWrapper的子类ContextThemeWrapper),但它们初始化的过程中都会创建ContextImpl对象,由ContextImpl实现Context中的方法
我们跟进ContextImpl源码:
class ContextImpl extends Context {
.......
//ServiceFetcher 通过getService获取服务对象
static class ServiceFetcher {
int mContextCacheIndex = -1;
public Object getService(ContextImpl ctx) {
ArrayList<Object> cache = ctx.mServiceCache;
Object service;
synchronized (cache) {
if (cache.size() == 0) {
for (int i = 0; i < sNextPerContextServiceCacheIndex; i++) {
cache.add(null);
}
} else {
//从缓存中获取Service对象
service = cache.get(mContextCacheIndex);
if (service != null) {
return service;
}
}
service = createService(ctx);
cache.set(mContextCacheIndex, service);
return service;
}
}
/**
* 子类中重写此方法来创建服务对象
*/
public Object createService(ContextImpl ctx) {
throw new RuntimeException("Not implemented");
}
}
//1、Service容器
private static final HashMap<String, ServiceFetcher> SYSTEM_SERVICE_MAP =
new HashMap<String, ServiceFetcher>();
private static int sNextPerContextServiceCacheIndex = 0;
//2、注册服务,添加到容器中
private static void registerService(String serviceName, ServiceFetcher fetcher) {
if (!(fetcher instanceof StaticServiceFetcher)) {
fetcher.mContextCacheIndex = sNextPerContextServiceCacheIndex++;
}
SYSTEM_SERVICE_MAP.put(serviceName, fetcher);
}
//3、静态语句块,注册各种服务,第一次加载该类时执行,并且只执行一次,保证实例的唯一性
static {
......
//注册LayoutInflater Service
registerService(LAYOUT_INFLATER_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
return PolicyManager.makeNewLayoutInflater(ctx.getOuterContext());
}});
......
//4、根据key获取对应的服务
public Object getSystemService(String name) {
ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);
return fetcher == null ? null : fetcher.getService(this);
}
从ContextImpl类中可以看到,虚拟机在以第一次加载类文件时会注册各种ServiceFatcher,这些服务会以键值对的方式存储在一个HashMap中,使用时可以调用getSystemService( )方法获取对应的ServiceFatcher,然后通过ServiceFatvher的createService方法创建服务对象,然后将该对象缓存到列表中,下次使用可直接从缓存中获取,避免重复创建对象。这其实就是上面我们所说的第七种单例创建方法即使用容器实现单例模式