Android设计模式详解之单例模式
创始人
2024-04-27 04:29:26
0

前言

定义:确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

使用场景:确保某个类有且仅有一个对象的场景,避免产生多个对象消耗过多的资源。比如要访问IO和数据库资源,应该考虑使用单例模式。

UML类图:
单例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关键字的作用:

  1. 禁止指令重排序;
    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失效!!

  1. 线程可见性,及时更新实例到主内存;【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 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源码中的单例模式

  1. 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> SYSTEM_SERVICE_FETCHERS =new ArrayMap>();...static {...registerService(Context.ACTIVITY_SERVICE, ActivityManager.class,new CachedServiceFetcher() {@Overridepublic ActivityManager createService(ContextImpl ctx) {return new ActivityManager(ctx.getOuterContext(), ctx.mMainThread.getHandler());}});registerService(Context.ACTIVITY_TASK_SERVICE, ActivityTaskManager.class,new CachedServiceFetcher() {@Overridepublic ActivityTaskManager createService(ContextImpl ctx) {return new ActivityTaskManager(ctx.getOuterContext(), ctx.mMainThread.getHandler());}});...}//注册serviceprivate static  void registerService(@NonNull String serviceName,@NonNull Class serviceClass, @NonNull ServiceFetcher 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_SERVICEACTIVITY_TASK_SERVICELAYOUT_INFLATER_SERVICE等等,使用的时候根据serviceNameArrayMap中查找到对应Service,
显然,这里的各个服务是单例的!~~

  1. LayoutInfater实例单例

我们经常使用LayoutInfater去绑定布局文件,如LayoutInflater.from(this).inflate(),但实际上LayoutInflater
对象也是个单例的,接下来我们就从源码的角度去验证;
LayoutInflater是个抽象类,他的实现类是PhoneLayoutInflater,我们还是回过头看下SystemServiceRegistry

static{registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,new CachedServiceFetcher() {@Overridepublic 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对象;

总结

单例模式是运用频率很高的模式;

它有以下优点:

  1. 内存中只有一个实例,减少了内存开销;
  2. 可以避免对资源的多重占用;
  3. 可以全局共享资源,方便数据访问;

同时他的缺点也显而易见:

  1. 一般没有接口,扩展性较差,扩展只能通过修改代码来实现;
  2. 如果持有Context,容易引发内存泄漏,注意传递给单例对象的Context应为Application Context

结语

如果以上文章对您有一点点帮助,希望您不要吝啬的点个赞加个关注,您每一次小小的举动都是我坚持写作的不懈动力!ღ( ´・ᴗ・` )

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...
有效的括号 一、题目 给定一个只包括 '(',')','{','}'...
【Ctfer训练计划】——(三... 作者名:Demo不是emo  主页面链接:主页传送门 创作初心ÿ...