当前位置: 首页 > news >正文

Android单例模式知识总结

六种核心实现方式对比

1. 饿汉式单例(Eager Initialization)

原理:利用类加载时静态变量初始化的特性,天然线程安全。
代码

public class EagerSingleton {private static final EagerSingleton INSTANCE = new EagerSingleton();private EagerSingleton() {} // 私有构造防止外部实例化public static EagerSingleton getInstance() { return INSTANCE; }
}

特点

  • 优点:简单可靠,线程安全,无需额外同步。
  • 缺点:类加载即初始化,浪费内存(适合小资源实例)。
  • 适用场景:程序启动时需初始化的全局配置类。

2. 懒汉式单例(非线程安全 vs 线程安全)

非线程安全(危险)

public class LazySingleton {private static LazySingleton instance;public static LazySingleton getInstance() {if (instance == null) instance = new LazySingleton(); // 多线程下可能创建多个实例return instance;}
}

线程安全(同步方法)

public class SynchronizedLazySingleton {private static SynchronizedLazySingleton instance;public static synchronized SynchronizedLazySingleton getInstance() {if (instance == null) instance = new SynchronizedLazySingleton();return instance;}
}

特点

  • 优点:延迟初始化,节省内存。
  • 缺点:同步方法性能差(每次调用都加锁),实际开发极少使用。

3. 双重检查锁定(DCL,线程安全)

核心代码

public class DCLSingleton {private static volatile DCLSingleton instance; // volatile 禁止指令重排序public static DCLSingleton getInstance() {if (instance == null) { // 第一次检查synchronized (DCLSingleton.class) { // 同步类对象,缩小锁范围if (instance == null) { // 第二次检查instance = new DCLSingleton(); // 非原子操作,需 volatile 保障}}}return instance;}
}

关键细节

  • volatile 防止指令重排序(如先赋值再初始化导致的空指针风险)。
  • 适用场景:性能敏感且需延迟初始化的场景(如网络管理器)。

4. 静态内部类单例(推荐)

原理:利用静态内部类的类加载机制(JVM 保证线程安全),延迟初始化。

public class StaticInnerClassSingleton {private StaticInnerClassSingleton() {}private static class Holder {static final StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton();}public static StaticInnerClassSingleton getInstance() {return Holder.INSTANCE; // 首次调用时加载 Holder 类,触发实例化}
}

特点

  • 兼顾饿汉式的线程安全与懒汉式的延迟初始化,实现优雅。
  • Android 中常用于工具类或轻量管理器。

5. 枚举单例(最安全)

Java 官方推荐(《Effective Java》)

public enum EnumSingleton {INSTANCE; // 枚举常量天然唯一,线程安全// 业务方法public void doSomething() { /* ... */ }
}

优势

  • 自动支持序列化和反序列化,防止反射攻击(Enum 禁止通过反射创建实例)。
  • 代码极简,无需额外处理线程安全。

6. Kotlin 单例(简洁高效)

伴生对象 + lazy 代理(懒汉式)

class KotlinSingleton {companion object {val instance: KotlinSingleton by lazy { KotlinSingleton() } // 线程安全,延迟初始化}
}

枚举实现

enum class KotlinEnumSingleton {INSTANCE;fun doSomething() = Unit
}

优势

  • by lazy 简化 DCL 逻辑,默认线程安全(可配置 LazyThreadSafetyMode)。
  • 枚举语法更简洁,天然防御反射和反序列化。

面试追问

一、基础概念与实现对比类真题

真题 1:饿汉式和懒汉式单例的核心区别是什么?适用场景如何选择?

解答:

  • 核心区别:

    • 初始化时机:饿汉式在类加载时立即创建实例(静态变量初始化),懒汉式在首次调用 getInstance() 时创建(延迟初始化)。
    • 线程安全:饿汉式依赖 JVM 类加载机制,天然线程安全;非同步懒汉式多线程下不安全,需通过 synchronized 或 DCL 保证安全。
    • 内存占用:饿汉式无论是否使用都占用内存(“饿”),懒汉式节省内存但实现复杂(“懒”)。
  • 适用场景选择:

    • 若实例占用资源少且希望提前初始化(如全局配置类),选饿汉式(简单可靠)。
    • 若实例创建耗时 / 耗资源(如网络管理器、图片加载引擎),且需延迟初始化,选DCL 懒汉式(兼顾性能与线程安全)。
    • 直接同步方法的懒汉式(synchronized 修饰方法)因性能差,实际开发几乎不用,仅作为概念对比。

考点分析:
考察对两种模式核心原理的理解,需结合 “类加载机制”“线程安全实现”“性能与内存权衡” 展开,避免仅停留在代码表面。

真题 2:DCL 单例为什么需要 volatile?不加会有什么问题?

解答:

  • 指令重排序风险:
    instance = new DCLSingleton(); 实际分三步:

    1. 分配内存空间(memory = allocate());
    2. 调用构造函数初始化对象(ctorInstance(memory));
    3. 将引用赋值给 instanceinstance = memory)。
      JVM 可能优化为 1→3→2(重排序),若线程 A 执行到步骤 3 时(instance 非空但未初始化),线程 B 调用 getInstance() 发现 instance 非空,直接返回未初始化的实例,导致空指针异常或逻辑错误。
  • volatile 的作用:
    禁止指令重排序,确保步骤按 1→2→3 执行;同时保证多线程间 instance 的可见性(一个线程修改后,其他线程立即感知)。

反例代码:
若不加 volatile,在高并发下可能返回未初始化的实例,典型面试陷阱!

考点分析:
深入考察 JVM 底层机制(指令重排序、可见性)与多线程安全,需结合底层原理解释,避免仅回答 “防止指令重排序” 的表面原因。

二、Android 特性与实战问题类真题

真题 3:Android 中使用单例时,如何避免内存泄漏?举例说明错误与正确做法。

解答:

  • 错误案例(Activity 上下文泄漏):

    public class BadSingleton {private Context context; // 持有 Activity 上下文(短生命周期)private static BadSingleton instance;private BadSingleton(Context context) {this.context = context; // 若传入 Activity,Activity 销毁后仍被单例引用,无法回收}public static BadSingleton getInstance(Context context) {if (instance == null) {instance = new BadSingleton(context);}return instance;}
    }
    
     

    问题: Activity 销毁时,单例仍持有其引用,导致 Activity 无法被 GC 回收,内存泄漏。

  • 正确做法:

    • 使用 Application 上下文(生命周期与应用一致):
      public class GoodSingleton {private Context context;private static GoodSingleton instance;private GoodSingleton(Context context) {this.context = context.getApplicationContext(); // 或直接传入 Application}public static GoodSingleton getInstance(Context context) {if (instance == null) {instance = new GoodSingleton(context);}return instance;}
      }
      
    • 若必须使用 Activity 上下文,确保单例不长期持有(如临时方法参数,而非成员变量)。

考点分析:
结合 Android 组件生命周期(Activity 短生命周期 vs Application 长生命周期),考察内存泄漏的根本原因及预防措施,是 Android 面试必考点。

真题 4:单例模式在 Android 中常用于哪些场景?举 3 个实际例子。

解答:

  1. 全局管理器
    • 网络请求管理器(如 Retrofit 封装类,统一管理 OkHttp 连接池);
    • 数据库助手(如 Room Database 的 DatabaseClient,避免重复创建连接)。
  2. 配置与状态管理
    • 应用主题 / 语言配置中心(存储全局配置,跨页面同步);
    • 用户登录状态管理器(确保各模块获取同一登录状态)。
  3. 轻量工具类
    • 日志工具(如统一的 Logger 类,控制日志级别和输出渠道);
    • Toast 管理类(避免多次创建 Toast 实例,保证显示顺序)。

扩展: 需说明选择单例的原因 —— 避免资源重复创建、提供全局访问点、维护统一状态,而非简单罗列场景。

三、线程安全与破坏防御类真题

真题 5:单例模式如何防止反射攻击?

解答:
反射可通过 newInstance() 调用私有构造函数创建新实例,破坏单例唯一性。防御措施:

  1. 在构造函数中检查实例是否已存在
    private static volatile Singleton instance;
    private Singleton() {if (instance != null) { // 防止反射多次创建throw new IllegalStateException("Instance already exists");}
    }
    

    注意:首次通过反射创建时,instance 为 null,可正常创建;再次反射时触发检查。
  2. 枚举单例(终极防御)
    Java 枚举天然防止反射创建实例(Enum.newInstance() 会抛出异常),是最安全的单例实现:
    public enum EnumSingleton {INSTANCE;// 业务方法
    }
    

考点分析:
考察单例的鲁棒性,需区分普通单例(需手动防御)与枚举单例(天然免疫)的差异,枚举是 Java 官方推荐的安全单例方式

真题 6:反序列化会破坏单例吗?如何解决?

解答:

  • 问题: 反序列化时,ObjectInputStream.readObject() 会创建新对象,导致单例失效。
  • 解决方案:
    1. 添加 readResolve() 方法,返回现有实例:
      private Object readResolve() {return instance; // 返回单例实例,避免创建新对象
      }
      
    2. 枚举单例无需额外处理,反序列化时直接返回枚举常量,天然支持单例。

代码示例:

public class SerializableSingleton implements Serializable {private static final long serialVersionUID = 1L;private static final SerializableSingleton INSTANCE = new SerializableSingleton();private SerializableSingleton() {}public static SerializableSingleton getInstance() { return INSTANCE; }// 防止反序列化创建新实例protected Object readResolve() { return INSTANCE; }
}

考点分析:
考察对 Java 序列化机制的理解,需明确单例在反序列化场景下的漏洞及修复方法,结合 readResolve 的作用深入解释。

相关文章:

  • 使用迁移学习的自动驾驶汽车信息物理系统安全策略
  • Java数据结构——Queue
  • LeetCode热题100--54.螺旋矩阵--中等
  • 商业中的人工智能 (AI) 是什么?
  • 大疆无人机(全系列,包括mini)拉流至电脑,实现直播
  • 【链表扫盲】FROM GPT
  • 第四章 OpenCV篇—图像梯度与边缘检测—Python
  • Rust包、crate与模块管理
  • 【 Redis | 实战篇 短信登录 】
  • CSS:元素显示模式与背景
  • 【图片合并PDF】一次性将多个文件夹里的图片批量按文件夹为单位合并PDF,多个文件夹图片合并PDF,基于WPF的实现方案
  • WPF中解决数据绑定不匹配的问题
  • 【wpf】11 在WPF中实现父窗口蒙版效果:原理详解与进阶优化
  • 【AI提示词】马斯洛需求分析专家
  • WPF主窗体子窗体关联方法
  • 华为云Astro后端开发中对象、事件、脚本、服务编排、触发器、工作流等模块的逻辑关系如何?以iotDA数据传输过程举例演示元素工作过程
  • 网易游戏 Flink 云原生实践
  • DeFi开发系统软件开发:技术架构与生态重构
  • redis多路复用IO模型 以及 6.0引入的多线程模型
  • Python pandas 向excel追加数据,不覆盖之前的数据
  • 中华人民共和国和俄罗斯联邦在纪念中国人民抗日战争、苏联伟大卫国战争胜利和联合国成立80周年之际关于进一步深化中俄新时代全面战略协作伙伴关系的联合声明
  • 晶圆销量上升,中芯国际一季度营收增长近三成,净利增超1.6倍
  • 北京:下调个人住房公积金贷款利率
  • 上海科创“八杰”赋能新兴产业链:硬核科技,形成良好盈利模式
  • 中方对中美就关税谈判的立场发生变化?外交部:中方立场没有任何改变
  • 个人住房公积金贷款利率下调,100万元30年期贷款总利息将减少近5万元