前言
定义:
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
使用场景:
确保某个类有且仅有一个对象的场景,避免产生多个对象消耗过多的资源。比如要访问IO和数据库资源,应该考虑使用单例模式。
UML类图:
实现方式
饿汉模式
/**
* 饿汉单例
*/
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return INSTANCE;
}
}
优点:
对象优先创建,无须等待,效率高。
缺点:
申明静态对象的时候就已经初始化,一定程度上造成了资源的浪费。
懒汉模式
/**
* 懒汉单例
*/
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
优点:
只有在使用时才会被实例化,一定程度上节约了资源。
缺点:
第一次加载时需要等待,同时每一次调用getInstance()
都进行同步,造成不必要的同步开销。
DCL(Double Check Lock)单例
/**
* DCL单例
*/
public class Singleton {
private volatile static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
volatile关键字的作用:
禁止指令重排序;
instance = new Singleton();
这句代码实际上并不是一个原子操作,最终会被编译成多条汇编指令,大致做了如下3件事;
- (1)给Singleton的实例分配内存;
- (2)调用Singleton()的构造函数,初始化成员字段;
- (3)将instance对象指向分配的内存空间(此时instance不再为null)
但是Java编译器允许处理器乱序执行,以上的执行顺序可能是1-2-3也可能是1-3-2,如果是后者,在线程A中当3执行完毕2未执行时,如果切换到线程B,此时instance已经非空,线程B直接取走instance,在使用就会出错,这就会导致DCL失效
!!
线程可见性,及时更新实例到主内存;
【JMM Java内存模型】
getInstance()方法中两次判空的作用:
第一次:
避免重复加锁,造成资源浪费;
第二次:
避免对象重复实例化;
优点:
资源利用率高,避免不必要的同步;
缺点:
第一次加载效率低,在某些情况下会出现DCL失效问题
;
静态内部类单例
/**
* 静态内部类单例
*/
public class Singleton {
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
}
优点:
懒加载,能够确保线程安全,推荐使用这种单例模式实现方式!
容器实现单例模式
/**
* 容器实现单例模式
* on 2022/12/20
*/
public class SingletonManager {
private static Map<String, Object> singletonMap = new HashMap<>();
private SingletonManager() {
}
public static void registerService(String key, Object instance) {
if (!singletonMap.containsKey(key)) {
singletonMap.put(key, instance);
}
}
public static Object getService(String key) {
return singletonMap.get(key);
}
}
优点:
可以管理多种类型的单例,使用时可以通过统一的接口进行获取操作,降低用户使用成本,也对用户隐藏了具体实现,降低耦合度。
Android源码中的单例模式
Context.getSystemService(String name)
这段代码我们经常使用去获取各种系统service,其实底层的具体实现就使用到了容器单例模式
,接下来我们就跟踪下源码,看下具体实现;
我们知道Context
是抽象类,他的实现类是ContextImpl
,其中ContextImpl.getSystemService(String name)
方法如下:
public Object getSystemService(String name) {
...
return SystemServiceRegistry.getSystemService(this, name);
}
我们继续分析SystemServiceRegistry.getSystemService(this, name)
public static Object getSystemService(ContextImpl ctx, String name) {
if (name == null) {
return null;
}
final ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
if (fetcher == null) {
...
return null;
}
final Object ret = fetcher.getService(ctx);
if (sEnableServiceNotFoundWtf && ret == null) {
...
return null;
}
return ret;
}
重点看下SYSTEM_SERVICE_FETCHERS
实例
...
private static final Map<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
new ArrayMap<String, ServiceFetcher<?>>();
...
static {
...
registerService(Context.ACTIVITY_SERVICE, ActivityManager.class,
new CachedServiceFetcher<ActivityManager>() {
@Override
public ActivityManager createService(ContextImpl ctx) {
return new ActivityManager(ctx.getOuterContext(), ctx.mMainThread.getHandler());
}});
registerService(Context.ACTIVITY_TASK_SERVICE, ActivityTaskManager.class,
new CachedServiceFetcher<ActivityTaskManager>() {
@Override
public ActivityTaskManager createService(ContextImpl ctx) {
return new ActivityTaskManager(
ctx.getOuterContext(), ctx.mMainThread.getHandler());
}});
...
}
//注册service
private static <T> void registerService(@NonNull String serviceName,
@NonNull Class<T> serviceClass, @NonNull ServiceFetcher<T> serviceFetcher) {
SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
SYSTEM_SERVICE_CLASS_NAMES.put(serviceName, serviceClass.getSimpleName());
}
可以看到SYSTEM_SERVICE_FETCHERS
是一个静态的ArrayMap
实例,在static静态代码块中,对系统中的各个服务进行注册,如我们熟悉的ACTIVITY_SERVICE
、ACTIVITY_TASK_SERVICE
、LAYOUT_INFLATER_SERVICE
等等,使用的时候根据serviceName
从ArrayMap
中查找到对应Service,
显然,这里的各个服务是单例的!~~
LayoutInfater
实例单例
我们经常使用LayoutInfater去绑定布局文件,如LayoutInflater.from(this).inflate()
,但实际上LayoutInflater
对象也是个单例的,接下来我们就从源码的角度去验证;
LayoutInflater
是个抽象类,他的实现类是PhoneLayoutInflater
,我们还是回过头看下SystemServiceRegistry
static{
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
new CachedServiceFetcher<LayoutInflater>() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
return new PhoneLayoutInflater(ctx.getOuterContext());
}});
}
在静态代码块中同样是实例化了全局唯一的PhoneLayoutInflater
对象,而我们调用的地方:
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;
}
正是通过LAYOUT_INFLATER_SERVICE
去查找对应的Service对象;
总结
单例模式是运用频率很高的模式;
它有以下优点:
- 内存中只有一个实例,减少了内存开销;
- 可以避免对资源的多重占用;
- 可以全局共享资源,方便数据访问;
同时他的缺点也显而易见:
- 一般没有接口,扩展性较差,扩展只能通过修改代码来实现;
- 如果持有
Context
,容易引发内存泄漏,注意传递给单例对象的Context应为Application Context
;
结语
如果以上文章对您有一点点帮助,希望您不要吝啬的点个赞加个关注,您每一次小小的举动都是我坚持写作的不懈动力!ღ( ´・ᴗ・` )