定义
单例模式是应用最广泛的设计模式之一。其确保单例类只有一个实例,而且自行实例化并向系统提供这个实例。当创建一个对象需要消耗很多资源时,可以考虑使用单例模式。
UML类图
单例模式的角色
-
Client
客户端类,即单例类的使用者。
-
Singleton
单例类,向客户端提供单例对象的实例。
单例模式的实现关键点
- 私有化单例类的构造函数,确保客户端不能通过new的方式实例化对象。
- 一般提供一个静态方法向客户端提供对象的实例。
- 确保单例类的对象有且只有一个,尤其在多线程情况下。
单例模式的实现方式
1.饿汉式
饿汉式在定义静态单例对象时就将其初始化,并在静态方法中返回。
/**
* 单例
* 饿汉式
*/
public class Singleton {
//声明静态对象并初始化
private static Singleton instance = new Singleton();
//私有化构造函数
private Singleton(){
}
//通过静态方法返回实例对象
public static Singleton getInstance() {
return instance;
}
}
2.懒汉式
懒汉式先定义静态单例对象,在单例类第一次使用时初始化对象,getInstance为同步方法,保证了多线程情况下单例对象的唯一性。
/**
* 单例
* 懒汉式
*/
public class Singleton {
private static Singleton instance;
private Singleton(){
}
private static synchronized Singleton getInstance(){
if (instance==null){
instance = new Singleton();
}
return instance;
}
}
该方式的优点是,单例对象在第一次使用时才初始化。其缺点是,每次调用getInstance方法都会进行同步,造成不必要的开销。
3.Double Check Lock(DCL)双重检查锁实现单例
/**
* 单例
* 双重检查
*/
public class Singleton {
private static volatile Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
DCL是开发中使用最多的实现单例的方式。getInstance中有两次判空,故其名曰双重检查。第一次判空是为了避免不必要的同步,第二次判空为了确保在实例为空的情况下再初始化单例对象。
但由java的指令重排序,可能会导致DCL检查失效,所以静态变量上要加volatile关键字确保安全。
4.静态内部类实现单例
/**
* 单例
* 静态内部类实现
*/
public class Singleton {
private Singleton(){
}
private Singleton getInstance(){
return SingletonHolder.instance;
}
private static class SingletonHolder{
private static Singleton instance = new Singleton();
}
}
第一次加载Singleton时并不会初始化instance,只有在第一次调用getInstance方法时会导致instance被初始化。这种方式不仅确保了线程安全,也保证了单例对象的唯一性,而且还延迟了单例的初始化。
5.通过枚举实现
/**
* 单例
* 定义枚举类实现
*/
public enum Singleton {
INSTANCE;
}
默认枚举实例的创建是线程安全的,在任何情况下它都是一个单例。对于其他几种实现单例的方式在反序列化时会重新创建对象,而枚举不存在这个问题。
6.使用容器实现单例模式
public class SingletonManager {
private static HashMap sMap = new HashMap<>();
private SingletonManager() {
}
public void registerService(String key, Object value) {
if (!sMap.containsKey(key)) {
sMap.put(key, value);
}
}
public Object getService(String key) {
return sMap.get(key);
}
}
这种方式可以将多种单例类型注入到统一的管理类中,在使用时根据key获取对应的单例类对象,在使用时通过统一的接口获取,降低了用户使用成本。
Android源码中的单例模式
LayoutInflater是Android提供的负责加载布局的服务,它也是一个单例。我们会通过LayoutInflater.from(this)
获得它的实例,from方法如下
/**
* Obtains the LayoutInflater from the given context.
*/
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来获得LayoutInflater实例,在分析装饰模式时,我们知道Context的实现类是ContextImpl,我们到ContextImpl中找到getSystemService方法
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
这里调用了SystemServiceRegistry对象的getSystemService方法
/**
* Gets a system service from a given context.
*/
public static Object getSystemService(ContextImpl ctx, String name) {
ServiceFetcher> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}
在这个方法中首先会获得ServiceFetcher对象,然后从fetcher中获得service对象。SYSTEM_SERVICE_FETCHERS是一个key为String,value为ServiceFetcher的HashMap,那现在的问题就是要搞明白ServiceFetcher对象是如何存到SYSTEM_SERVICE_FETCHERS这个Map中的。SystemServiceRegistry中的registerService方法如下
private static void registerService(String serviceName, Class serviceClass,
ServiceFetcher serviceFetcher) {
SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
}
就是在这个方法中调用了SYSTEM_SERVICE_FETCHERS的put方法将ServiceFetcher对象存入。而registerService方法又在SystemServiceRegistry的静态代码块中调用,如下
static {
//...
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
new CachedServiceFetcher() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
return new PhoneLayoutInflater(ctx.getOuterContext());
}});
//...
}
那么当SystemServiceRegistry类加载时,这里的静态代码块执行,并完成各个系统服务的注册,由于静态代码块只执行一次,所以也满足了单例的要求。