【Android设计模式应用】谈谈Android中的单例模式

【Android设计模式应用】谈谈Android中的单例模式

2023年6月28日发(作者:)

【Android设计模式应⽤】谈谈Android中的单例模式简单定义对于单例模式,各位开发同学们已经熟的不能在熟了吧,也是⼴泛应⽤在各类项⽬中,⽆论是使⽤什么⾼级编程语⾔,设计模式总是伴随其左右。简单来说,单例模式就是确保⼀个类中只有⼀个实例化对象,然后提供⼀个全局可以访问点就ok了单例模式的优缺点1. 主要优点:提供了对唯⼀实例的受控访问。由于在系统内存中只存在⼀个对象,因此可以节约系统资源,对于⼀些需要频繁创建和销毁的对象单例模式⽆疑可以提⾼系统的性能。允许可变数⽬的实例。2. 主要缺点:由于单利模式中没有抽象层,因此单例类的扩展有很⼤的困难。单例类的职责过重,在⼀定程度上违背了“单⼀职责原则”。滥⽤单例将带来⼀些负⾯问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多⽽出现连接池溢出;如果实例化的对象长时间不被利⽤,系统会认为是垃圾⽽被回收,这将导致对象状态的丢失。谈谈java和kotlin的实现⽅式Kotlin中的⼏种单例模式饿汉式单例饿汉式单例模式是实现单例模式⽐较简单的⼀种⽅式,它有个特点就是不管需不需要该单例实例,该实例对象都会被实例化。在kotlin中实现起来⾮常简单,只需要定义⼀个object对象表达式即可,⽆需⼿动去设置构造器私有化和提供全局访问点,这⼀点Kotlin编译器全给你做好了object KSingleton : Serializable {//实现Serializable序列化接⼝,通过私有、被实例化的readResolve⽅法控制反序列化 fun doSomething() { println("do some thing") } private fun readResolve(): Any {//防⽌单例对象在反序列化时重新⽣成对象 return KSingleton//由于反序列化时会调⽤readResolve这个钩⼦⽅法,只需要把当前的KSingleton对象返回⽽不是去创建⼀个新的对象 }}线程安全的懒汉式单例当类加载的时候就去创建这个单例实例,当我们使⽤这个实例的时候才去初始化它class KLazilySingleton private constructor() : Serializable { fun doSomething() { println("do some thing") } companion object { private var mInstance: KLazilySingleton? = null get() { return field ?: KLazilySingleton() } @JvmStatic @Synchronized//添加synchronized同步锁 fun getInstance(): KLazilySingleton { return requireNotNull(mInstance) } } //防⽌单例对象在反序列化时重新⽣成对象 private fun readResolve(): Any { return tance() }}DCL(double check lock)改造懒汉式单例线程安全的单例模式直接是使⽤synchronized同步锁,锁住getInstance⽅法,每⼀次调⽤该⽅法的时候都得获取锁,但是如果这个单例已经被初始化了,其实按道理就不需要申请同步锁了,直接返回这个单例类实例即可。于是就有了DCL实现单例⽅式⽽且在kotlin中,可以⽀持线程安全DCL的单例,可以说也是⾮常⾮常简单,就仅仅3⾏代码左右,那就是Companion Object + lazy属性代理,⼀起来看下吧class KLazilyDCLSingleton private constructor() : Serializable {//private constructor()构造器私有化 fun doSomething() { println("do some thing") } private fun readResolve(): Any {//防⽌单例对象在反序列化时重新⽣成对象 return instance } companion object { //通过@JvmStatic注解,使得在Java中调⽤instance直接是像调⽤静态函数⼀样, //类似tance(),如果不加注解,在Java中必须这样调⽤: tance(). @JvmStatic //使⽤lazy属性代理,并指定LazyThreadSafetyMode为SYNCHRONIZED模式保证线程安全 val instance: KLazilyDCLSingleton by lazy(ONIZED) { KLazilyDCLSingleton() } }}静态内部类单例DCL虽然在⼀定程度上能解决资源消耗、多余synchronized同步、线程安全等问题,但是某些情况下还会存在DCL失效问题在多线程环境下⼀般不推荐DCL的单例模式。所以引出静态内部类单例实现class KOptimizeSingleton private constructor(): Serializable {//private constructor()构造器私有化 companion object { @JvmStatic fun getInstance(): KOptimizeSingleton {//全局访问点 return nce } } fun doSomething() { println("do some thing") } private object SingletonHolder {//静态内部类 val mInstance: KOptimizeSingleton = KOptimizeSingleton() } private fun readResolve(): Any {//防⽌单例对象在反序列化时重新⽣成对象 return nce }}枚举单例枚举单例实现,就是为了防⽌反序列化,因为我们都知道枚举类反序列化是不会创建新的对象实例的枚举类型的序列化机制保证只会查找已经存在的枚举类型实例,⽽不是创建新的实例enum class KEnumSingleton { INSTANCE; fun doSomeThing() { println("do some thing") }}Java中的⼏种单例模式懒汉式(线程不安全)//懒汉式单例类.在第⼀次调⽤的时候实例化⾃⼰

public class Singleton {

//私有的构造函数 private Singleton() {}

//私有的静态变量

private static Singleton single=null;

//暴露的公有静态⽅法

public static Singleton getInstance() {

if (single == null) {

single = new Singleton();

}

return single;

}

}⼀般来说懒汉式分为三个部分,私有的构造⽅法,私有的全局静态变量,公有的静态⽅法起到重要作⽤的是静态修饰符static关键字,我们知道在程序中,任何变量或者代码都是在编译时由系统⾃动分配内存来存储的,⽽所谓静态就是指在编译后所分配的内存会⼀直存在,直到程序退出内存才会释放这个空间,因此也就保证了单例类的实例⼀旦创建,便不会被系统回收,除⾮⼿动设置为null。这种⽅式创建的缺点是存在线程不安全的问题,解决的办法就是使⽤synchronized 关键字,便是单例模式的第⼆种写法了。懒汉式(线程安全)public class Singleton {

//私有的静态变量 private static Singleton instance;

//私有的构造⽅法 private Singleton (){}; //公有的同步静态⽅法 public static synchronized Singleton getInstance() {

if (instance == null) {

instance = new Singleton();

}

return instance;

}

}

这种单例实现⽅式的getInstance()⽅法中添加了synchronized 关键字,也就是告诉Java(JVM)getInstance是⼀个同步⽅法。同步的意思是当两个并发线程访问同⼀个类中的这个synchronized同步⽅法时,⼀个时间内只能有⼀个线程得到执⾏,另⼀个线程必须等待当前线程执⾏完才能执⾏,因此同步⽅法使得线程安全,保证了单例只有唯⼀个实例。但是它的缺点在于每次调⽤getInstance()都进⾏同步,造成了不必要的同步开销。这种模式⼀般不建议使⽤。饿汉式(线程安全)//饿汉式单例类.在类初始化时,已经⾃⾏实例化

public class Singleton {

//static修饰的静态变量在内存中⼀旦创建,便永久存在 private static Singleton instance = new Singleton();

private Singleton (){}

public static Singleton getInstance() {

return instance;

}

}

饿汉式在类创建的同时就已经创建好⼀个静态的对象供系统使⽤,以后不再改变,所以天⽣是线程安全的。其中instance=new Singleton()可以写成: static {

instance = new Singleton();

}

属于变种的饿汉单例模式,也是基于classloder机制避免了多线程的同步问题,instance在类装载时就实例化了。DCL双重校验模式public class Singleton {

private static Singleton singleton; //静态变量 private Singleton (){} //私有构造函数 public static Singleton getInstance() {

if (singleton == null) { //第⼀层校验 synchronized () {

if (singleton == null) { //第⼆层校验 singleton = new Singleton();

}

}

}

return singleton;

}

}

这种模式的特殊之处在于getInstance()⽅法上,其中对singleton进⾏了两次判断是否空,第⼀层判断是为了避免不必要的同步,第⼆层的判断是为了在null的情况下才创建实例。具体我们来分析⼀下:假设线程A执⾏到了singleton = new Singleton(); 语句,这⾥看起来是⼀句代码,但是它并不是⼀个原⼦操作,这句代码最终会被编译成多条汇编指令,它⼤致会做三件事情:1. 给Singleton的实例分配内存2. 调⽤Singleton()的构造函数,初始化成员字段3. 将singleton对象指向分配的内存空间(即singleton不为空了)但是在JDK1.5之后,官⽅给出了volatile关键字,将singleton定义的代码,为了解决DCL失效的问题。private volatile static Singleton singleton; //使⽤volatile 关键字静态内部类单例模式public class Singleton {

private Singleton (){} ;//私有的构造函数 public static final Singleton getInstance() {

return CE;

}

//定义的静态内部类 private static class SingletonHolder {

private static final Singleton INSTANCE = new Singleton(); //创建实例的地⽅ }

}

第⼀次加载Singleton 类的时候并不会初始化INSTANCE ,只有第⼀次调⽤Singleton 的getInstance()⽅法时才会导致INSTANCE 被初始化。因此,第⼀次调⽤getInstance()⽅法会导致虚拟机加载SingletonHolder 类,这种⽅式不仅能够确保单例对象的唯⼀性,同时也延迟了单例的实例化。枚举单例前⾯的⼏种单例模式实现⽅式,⼀般都会稍显⿇烦,或是在某些特定的情况下出现⼀些状况。下⾯介绍枚举单例模式的实现:public enum Singleton { //enum枚举类 INSTANCE;

public void whateverMethod() {

}

}枚举单例模式最⼤的优点就是写法简单,枚举在java中与普通的类是⼀样的,不仅能够有字段,还能够有⾃⼰的⽅法,最重要的是默认枚举实例是线程安全的,并且在任何情况下,它都是⼀个单例。即使是在反序列化的过程,枚举单例也不会重新⽣成新的实例。⽽其他⼏种⽅式,必须加⼊如下⽅法:private Object readResolve() throws ObjectStreamException{ return INSTANCE;}这样的话,才能保证反序列化时不会⽣成新的⽅法使⽤容器实现单例模式public class SingletonManager {

  private static Map objMap = new HashMap();//使⽤HashMap作为缓存容器  private Singleton() {

  }  public static void registerService(String key, Objectinstance) {    if (!nsKey(key) ) {      (key, instance) ;//第⼀次是存⼊Map    }  }  public static ObjectgetService(String key) {    return (key) ;//返回与key相对应的对象  }}在程序的初始,将多种单例模式注⼊到⼀个统⼀的管理类中,在使⽤时根据key获取对应类型的对象。场景应⽤1.那么什么时候需要考虑使⽤单例模式呢?系统只需要⼀个实例对象,如系统要求提供⼀个唯⼀的* 序列号⽣成器或资源管理器,或者需要考虑资源消耗太⼤⽽只允许创建⼀个对象。客户调⽤类的单个实例只允许使⽤⼀个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。2.下⾯我们结合Android中⼀些源码来分析⼀下下Android中常⽤的EventBus框架我们可以看看EventBus中的如何使⽤单例模式的,主要是使⽤双重检查DCLstatic volatile EventBus defaultInstance; public static EventBus getDefault() { if (defaultInstance == null) { synchronized () { if (defaultInstance == null) { defaultInstance = new EventBus(); } } } return defaultInstance; }这样的话它的资源利⽤率会很⾼,并且第⼀次执⾏的时候,是单例对象才会被实例化,但是第⼀次加载的时候会稍慢,可以被接受LayouInflater的单例模式实现基本⽤法LayoutInflater mInflater = (this);上边的写法估计没有⼈会陌⽣,获取LayoutInflater 的实例,我们⼀起看看具体的源码实现:1.通过(context)来获取LayoutInflater服务/** * Obtains the LayoutInflater from the given context. */public static LayoutInflater from(Context context) { LayoutInflater LayoutInflater = (LayoutInflater) temService(_INFLATER_SERVICE); if (LayoutInflater == null) { throw new AssertionError("LayoutInflater not found."); } return LayoutInflater;}2.看看temService是怎么⼯作的,context的实现类是ContextImpl类,点进去看⼀下@Overridepublic Object getSystemService(String name) { return temService(this, name);}3.进⼊到SystemServiceRegistry类中/** * Gets a system service from a given context. */public static Object getSystemService(ContextImpl ctx, String name) { ServiceFetcher fetcher = SYSTEM_SERVICE_(name); return fetcher != null ? vice(ctx) : null;}到这⾥了相信各位已经感觉这是上述说的使⽤容器实现单例模式了,对⽐⼀下,果然是private static final HashMap> SYSTEM_SERVICE_FETCHERS = new HashMap>();使⽤map通过键值对的⽅式保存系统服务。在调⽤registerService的时候注⼊。/** * Statically registers a system service with the context. * This method must be called during static initialization only. */private static void registerService(String serviceName, Class serviceClass, ServiceFetcher serviceFetcher) { SYSTEM_SERVICE_(serviceClass, serviceName); SYSTEM_SERVICE_(serviceName, serviceFetcher);}4.我们可以再看看这些服务都是在什么时候注册的static {registerService(_INFLATER_SERVICE, , new CachedServiceFetcher() { @Override public LayoutInflater createService(ContextImpl ctx) { return new PhoneLayoutInflater(erContext()); }});}很明显,这是在⼀个静态的代码块进⾏注册服务,第⼀次加载该类的时候执⾏,并且只执⾏⼀次,保证实例的唯⼀性从这个过程中可以看出,系统将服务以键值对的形式存储在HashMap中,⽤户使⽤时只需要获取具体的服务对象,第⼀次获取时,调⽤getSystemService来获取具体的对象,在第⼀次调⽤时,会调⽤registerService通过map将对象缓存在⼀个列表中,下次再⽤时直接从容器中取出来就可以。避免重复创建对象,从⽽达到单例的效果。减少了资源消耗。在Android源码中,APP启动的时候,虚拟机第⼀次加载该类时会注册各种ServiceFetcher,⽐如LayoutInflater Service。将这些服务以键值对的形式存储在⼀个HashMap中,⽤户使⽤时只需要根据key来获取到对应的ServiceFetcher,然后通过ServiceFetcher对象的getService函数获取具体的服务对象。当第⼀次获取时,会调⽤ServiceFetcher的creatService函数创建服务对象,然后将该对象缓存到⼀个列表中,下次再取时直接从缓存中获取,避免重复创建对象,从⽽达到单例的效果。Android中的系统核⼼服务以单例形式存在,减少了资源消耗。ImageLoader图⽚加载框架图⽚加载框架ImageLoader的实例创建就是使⽤了单例模式,因为这个ImageLoader中含有线程池、缓存系统、⽹络请求,很消耗资源,不应该创建多个对象,这时候就需要⽤到单例模式tance();// 在⾃⼰的Application中创建全局实例.....//getInstance()执⾏的源码 public static ImageLoader getInstance() { if(instance == null) {//双重校验DCL单例模式 Class var0 = ; synchronized() {//同步代码块 if(instance == null) { instance = new ImageLoader();//创建⼀个新的实例 } } } return instance;//返回⼀个实例 }因此,如果我们创建⼀个对象需要消耗过多的资源的话,可以考虑单例模式结语以上都是同学我结合⽹上资料进⾏整理的单例模式的分析总结,简单来说,单例模式在设计模式中是⾮常常见的,它简单但同时⼜简单,值得我们继续深⼊探讨下去,下⾯我简单总结⼀下单例模式⼀个核⼼原理就是私有构造,并且通过静态⽅法获取⼀个实例。在这个过程中必须保证线程的安全性。推荐使⽤静态内部内实现单例或加了Volatile关键字的双重检查单例

发布者:admin,转转请注明出处:http://www.yc00.com/news/1687956222a60700.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信