常见单例模式详解

单例模式是23种设计模式中应用最广的模式之一,其定义:确保某一个类只有一个实例,而且自行实实例化并向整个系统通过这个实例。其类图如下:

通俗来说,单例模式就是用于创建那些在软件系统中独一无二的对象。在一个软件系统中,往往无需创建多个实例。举个大家熟悉的例子--- Windows任务管理器。有兴趣的可以试下,按住Ctrl + Alt + Del然后在弹出的界面中选择任务管理器或者在菜单栏右键弹出菜单上多次点击启动任务管理器,你会发现,无论启动多少次,Windows系统只会弹出一个任务管理器窗口,这是日常生活最常见的单例模式应用之一。采用单例模式可以避免产生多个对象而导致消耗过多的资源问题,比如要进行IO访问操作或便利查询数据库等,这时单例模式就是一个较好的解决方案。

实现方式

1、构造方法属性改为private;

2、通过一个静态方法返回一个全局单例类对象;

3、在系统中无论何种情况下或在子线程中,单例对象都只有一个,不会重复创建单例对象。

而根据其实现方式细节不同,又可分为以下几种:

饿汉式

复制代码
public class Singletion {

    private Singletion() {

    }
    
    private static final Singletion mInstance = new Singletion();

    public static Singletion getInstance() {
        return mInstance;
    }
}

此方式在声明静态对象时就初始化(在类装载(ClassLoader)时就构建,也可以说预先加载),通过static关键字修饰静态变量,将其存储在内存中,确保只有一份数据。

而final关键字,使得只初始化一次,所以mInstance实例只有一个。

此方式线程安全,由于在类加载的同时就已经创建好一个静态对象,所以调用时耗时短、速度快 (优点)。

但也有可能getInstance()永远不会执行,但执行该类的其他静态方法或者加载了该类(class.forName),那么这个类仍然会初始化,可能会浪费资源(缺点)。

懒汉式

复制代码
public class Singletion {
    
    private  Singletion() {}

    private static Singletion mInstance;

    public static synchronized Singletion getInstance() {
        if (mInstance == null) {
            mInstance = new Singletion();
        }
        return mInstance;
    }
}

该类在调用getInstance的时候(使用)才初始化,但这里加了synchronized关键字,就变成了一个同步方法。相较于饿汉式的"空间换时间"特点,懒汉式是"时间换空间"。

由于在使用时才会进行实例化,可以说节省了系统资源(优点);

但每次调用getInstance都会同步一次,浪费系统资源(缺点)。

双重检测加锁方式

复制代码
public class Singletion {

    private Singletion() {}

    private volatile static Singletion mInstance;
    
    public static Singletion getInstance() {
        if (mInstance == null) {
            synchronized (Singletion.class) {                
                if (mInstance == null) {
                    mInstance = new Singletion ();                
                }
            }
        }
        return mInstance;
    }
}

这里使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行(指令重排单线程环境不会出问题,但是多线程场景下会导致一个线程获得还没有初始化的实例),举个例子:

复制代码
...
private static Singletion mInstance;
private Singletion() {}
public static Singletion getInstance() {...}
...

由于JVM是可以乱序执行方法的,上面三句方法在执行过程可能出现下面场景:
如果A线程执行getInstance(),还没执行构造方法Singletion(),此时B线程调用getInstance(),因为A线程已经执行了getInstance(),所以mInstance不为空就直接获取到实例,由于B线程直接获取,而真实情况是A线程构造方法还未执行,所以mInstance就为空了

虽然概率较小,但也有可能发生,故JDK自1.6开始加入volatile关键字,虽然必不可免的会消耗一些性能。

此方式资源利用率高 ,不执行getInstance()就不会被实例,可以执行该类的其他静态方法 (优点);

但同时也存在第一次加载时较慢多线程使用会有不必要的同步开销的问题(缺点)。

静态内部类方式

复制代码
class Singletion {
    private Singletion() {
    }

    private static class SingletonLoader {
        static Singletion mInstance = new Singletion();
    }

    public static Singletion getInstance() {
        return SingletonLoader.mInstance;
    }
}

此类在调用getInstance的时候才初始化,调用getInstance才会去加载SingletonLoader类,确保了线程安全、单例的唯一性。

由此可见,这种写法不执行getInstance()则不被实例化,可以执行该类其他静态方法,避免资源浪费(优点);

但第一次加载速度肯定不够快(缺点)。

总结

其实不管哪种实现方式,其核心思想是一样的,私有化构造方法,然后通过静态方法返回唯一对象实例,同时保证线程安全。

具体使用哪种方式要看应用场景。有的场景适合饿汉式,有的对资源加载有要求的可以采用静态内部类方式。

其实Android系统中就有很多单例模式的运用,包括日常的APP开发中的Application也是常见的单例模式。还有很多Context调用的系统服务等,比如LayoutInflater服务

相关推荐
TeleostNaCl2 分钟前
如何安装 Google 通用的驱动以便使用 ADB 和 Fastboot 调试(Bootloader)设备
android·经验分享·adb·android studio·android-studio·android runtime
fatiaozhang95271 小时前
中国移动浪潮云电脑CD1000-系统全分区备份包-可瑞芯微工具刷机-可救砖
android·网络·电脑·电视盒子·刷机固件·机顶盒刷机
土了个豆子的1 小时前
02.继承MonoBehaviour的单例模式基类
开发语言·visualstudio·单例模式·c#·里氏替换原则
2501_915918412 小时前
iOS 开发全流程实战 基于 uni-app 的 iOS 应用开发、打包、测试与上架流程详解
android·ios·小程序·https·uni-app·iphone·webview
lichong9512 小时前
【混合开发】vue+Android、iPhone、鸿蒙、win、macOS、Linux之dist打包发布在Android工程asserts里
android·vue.js·iphone
Android出海2 小时前
Android 15重磅升级:16KB内存页机制详解与适配指南
android·人工智能·新媒体运营·产品运营·内容运营
一只修仙的猿2 小时前
毕业三年后,我离职了
android·面试
编程乐学3 小时前
安卓非原创--基于Android Studio 实现的新闻App
android·ide·android studio·移动端开发·安卓大作业·新闻app
雅雅姐3 小时前
Android14 init.rc中on boot阶段操作4
android
fatiaozhang95274 小时前
中国移动中兴云电脑W132D-RK3528-2+32G-刷机固件包(非原机制作)
android·xml·电脑·电视盒子·刷机固件·机顶盒刷机