当前位置: 首页 > 科技观察

说说AndroidJava语言中单例的设计模式

时间:2023-03-23 11:31:04 科技观察

随着我们深入编写代码,或多或少都会接触到设计模式,其中单例模式应该是我们比较熟悉的一种模式。本文将专门介绍Java设计模式中的单例模式。概念单例模式,也称为单例模式或单子模式,是指一个只有一个实例的类,并提供一个全局访问点。实现思路在单例类中设置一个私有静态变量sInstance,sInstance的类型为当前类,用于保存单例***的实例。将(无参数)构造函数设为私有,以避免外部使用new构造多个实例。提供一个公共静态方法,如getInstance,返回该类的最佳实例sInstance。上面的单例实例可以通过以下几种形式创建,每个实现都需要保证实例的唯一性。饿了么中式饿了么中式是指类加载时创建单例实例。如果单例类的构造方法没有包含过多的操作和处理,饿汉式其实是可以接受的。饿了么中式常用代码如下。当加载SingleInstance类时,会执行privatestaticSingleInstancesInstance=newSingleInstance();初始化***实例,然后getInstance()可以直接返回sInstance。publicclassSingleInstance{privatestaticSingleInstancesInstance=newSingleInstance();privateSingleInstance(){}publicstaticSingleInstancegetInstance(){returnsInstance;}}饿了么样式问题如果在构造方法中处理过多,会导致该类加载变慢,可能会导致性能问题。如果使用饿汉式,只加载类,没有实际调用,会造成资源浪费。懒惰风格懒惰风格是指单例实例在第一次使用时创建。这样一来,就避免了上述饿汉式可能遇到的问题。但是考虑到多线程的并发操作,我们不能像下面的代码那样简单的实现。publicclassSingleInstance{privatestaticSingleInstancesInstance;privateSingleInstance(){}publicstaticSingleInstancegetInstance(){if(null==sInstance){sInstance=newSingleInstance();}returnsInstance;}}当多个线程密集调用getInstance时,上述代码可能会创建多个实例。比如线程A进入了null==sInstance的代码块,当线程A还没有创建实例的时候,如果线程B也进入了这个代码块,必然会产生两个实例。使用synchronized修改getInstance方法的synchronized修改方法可能是保证多线程单例完整性的最简单方法。synchronized修饰的方法后,当有线程进入并调用该方法时,只有其他线程离开当前方法后,该线程才会进入该方法。所以保证任何时候只有一个线程进入getInstance。publicclassSingleInstance{privatestaticSingleInstancesInstance;privateSingleInstance(){}publicstaticsynchronizedSingleInstancegetInstance(){if(null==sInstance){sInstance=newSingleInstance();}returnsInstance;}}但是使用synchronized修改getInstance方法必然会导致性能下降,getInstance是经常调用的方法。这种方法虽然可以解决问题,但是不推荐。双重检查锁定使用双重检查锁定。首先,在进入该方法时检查null==sInstance。如果第一次检查通过,即没有创建实例,则进入synchronized控制的同步块,再次检查实例是否创建。如果尚未创建,请创建实例。Double-checklocking保证多线程下只创建一个实例,锁代码块只在实例创建前同步。如果在创建实例后进入方法,则不会执行同步块之前的代码。publicclassSingleInstance{privatestaticvolatileSingleInstancesInstance;privateSingleInstance(){}publicstaticSingleInstancegetInstance(){if(null==sInstance){synchronized(SingleInstance.class){if(null==sInstance){sInstance=newSingleInstance();}}}voreturns}Instance};什么是Volatile,是轻量级的synchronized,保证了多处理器开发中共享变量的“可见性”。可见性意味着当一个线程修改共享变量时,另一个线程可以读取修改后的值。使用volatile修改sInstance变量后,可以保证sInstance变量在多线程之间被正确处理。关于volatile,可以访问深度解析Volatile的实现原理了解更多。利用Java中的静态机制,类的静态初始化会在类加载时触发。利用这个原理,我们可以利用这个特性。结合内部类,我们可以实现如下代码实现惰性创建实例。publicclassSingleInstance{privateSingleInstance(){}publicstaticSingleInstancegetInstance(){returnSingleInstanceHolder.sInstance;}privatestaticclassSingleInstanceHolder{privatestaticSingleInstancesInstance=newSingleInstance();}}关于这个机制,大家可以详细了解double-checklocking和lazyinitialization。单例模式不保证实例的完整性,只要想办法,还是可以打破这种完整性的。可以通过以下方法实现。使用反射,虽然构造函数是非public的,但是在反射面前是不会起作用的。如果单例类实现了cloneable,仍然可以复制多个实例。Java中的对象序列化也有可能创建多个实例。避免使用readObject方法。使用多个类加载器加载一个单例类也会造成创建多个实例共存的问题。单例可以继承吗?单例类能否被继承要看情况。继承是可能的当子类是父单例类的内部类时,继承是可能的。publicclassBaseSingleton{privatestaticvolatileBaseSingletonsInstance;privateBaseSingleton(){}publicstaticBaseSingletongetInstance(){if(null==sInstance){synchronized(BaseSingleton.class){if(null==sInstance){sInstance=newBaseSingleton();}}}myreturnsInstance;但是上面只允许编译执行,继承一个单例没有实际意义,会事半功倍,而且成本比写一个新的单例类要大。有兴趣的童鞋可以尝试折腾。不允许继承。如果子类是一个单独的类,而不是单例类的内部类,那么编译时就会出错。隐式超级构造函数BaseSingleton()对于默认构造函数不可见。必须定义显式构造函数,主要原因是单例类的构造函数是私有的。解决方法是将构造函数设置为可见,但这并不能保证单例的唯一性。所以这个方法不能被继承。一般来说,单例类不应该被继承。单例vs静态变量全局静态变量也可以达到单例的效果,但是使用全局变量不能保证只会创建一个实例,而且使用全局变量需要团队约束,在执行上可能会出现问题。关于GC,因为单例类中的另一个静态变量持有单例实例,所以单例对象比普通对象更不容易被GC回收。单例对象的回收应该发生在它的类加载器被GC回收之后,一般不容易发生。