三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

单例模式详解:原理、实现与Android实践

单例模式详解:原理、实现与Android实践

1. 单例模式核心概念解析

单例模式(Singleton Pattern)是设计模式中最简单却又最常被讨论的模式之一。这种模式的核心在于确保一个类只有一个实例,并提供一个全局访问点。在实际开发中,我见过太多因为滥用单例导致的线程安全问题,也见证过合理使用单例带来的便利。

为什么需要单例?想象你正在开发一个打印机管理程序。如果允许创建多个打印管理对象,可能会导致打印任务冲突、资源浪费等问题。这时单例模式就能确保整个应用程序中只有一个打印管理器实例,所有打印请求都通过这个唯一实例来处理。

2. 单例模式的两种基础实现方式

2.1 饿汉式单例

饿汉式是最直接的单例实现方式,它在类加载时就创建实例。这种方式简单粗暴,但可能会造成资源浪费——如果这个实例最终没有被使用。

public class EagerSingleton { private static final EagerSingleton instance = new EagerSingleton(); private EagerSingleton() {} public static EagerSingleton getInstance() { return instance; } }

注意:饿汉式的构造方法必须声明为private,防止外部通过new创建实例。这是所有单例实现的基础要求。

2.2 懒汉式单例

与饿汉式不同,懒汉式单例在第一次被调用时才创建实例。这种方式更符合"按需创建"的原则,但需要考虑线程安全问题。

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

在实际项目中,我通常会根据以下原则选择实现方式:

  • 如果实例创建开销小且确定会被使用 → 选择饿汉式
  • 如果实例创建开销大或可能不会被使用 → 选择懒汉式

3. 单例模式的进阶实现与优化

3.1 双重检查锁定(DCL)

直接给getInstance方法加synchronized虽然能保证线程安全,但每次调用都会带来性能开销。双重检查锁定是一种优化方案:

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

这里有几个关键点:

  1. volatile关键字防止指令重排序
  2. 第一次检查避免不必要的同步
  3. 第二次检查确保只有一个实例被创建

3.2 静态内部类实现

这是我最推荐的单例实现方式,它结合了饿汉式的线程安全和懒汉式的延迟加载优点:

public class InnerClassSingleton { private InnerClassSingleton() {} private static class Holder { private static final InnerClassSingleton INSTANCE = new InnerClassSingleton(); } public static InnerClassSingleton getInstance() { return Holder.INSTANCE; } }

这种实现方式利用了类加载机制保证线程安全,只有在调用getInstance()时才会加载Holder类并创建实例。

4. 单例模式在Android开发中的实践

在Android Studio中使用单例模式时,有几个特殊注意事项:

  1. Application Context的使用:
public class AppSingleton { private static AppSingleton instance; private Context appContext; private AppSingleton(Context context) { this.appContext = context.getApplicationContext(); } public static synchronized AppSingleton getInstance(Context context) { if (instance == null) { instance = new AppSingleton(context); } return instance; } }

重要提示:永远不要持有Activity的context,这会导致内存泄漏。应该使用Application Context。

  1. 单例与Android组件生命周期:
  • 避免在单例中直接持有View或Activity引用
  • 考虑使用WeakReference处理可能需要的上下文引用

5. 单例模式的常见问题与解决方案

5.1 反射攻击防护

通过反射可以绕过private构造方法,破坏单例。防护方法:

public class ReflectionProofSingleton { private static ReflectionProofSingleton instance; private ReflectionProofSingleton() { if (instance != null) { throw new RuntimeException("Use getInstance() method to get the single instance"); } } public static synchronized ReflectionProofSingleton getInstance() { if (instance == null) { instance = new ReflectionProofSingleton(); } return instance; } }

5.2 序列化问题

如果单例类实现了Serializable接口,反序列化时会创建新实例。解决方法:

public class SerializableSingleton implements Serializable { private static final long serialVersionUID = 1L; private SerializableSingleton() {} private static class Holder { private static final SerializableSingleton INSTANCE = new SerializableSingleton(); } public static SerializableSingleton getInstance() { return Holder.INSTANCE; } protected Object readResolve() { return getInstance(); } }

5.3 多ClassLoader环境

在OSGi或某些服务器环境中,不同的ClassLoader可能导致多个单例实例。解决方案是明确指定ClassLoader或使用上下文ClassLoader。

6. 单例模式的最佳实践

经过多年实践,我总结了以下单例模式使用准则:

  1. 优先考虑静态内部类实现方式
  2. 除非必要,否则不要实现Serializable接口
  3. 在Android中谨慎处理Context引用
  4. 考虑使用依赖注入框架管理单例
  5. 单例应该保持轻量级,避免存储大量数据
  6. 单元测试时要能够重置单例状态

对于现代Java开发,我越来越倾向于使用枚举实现单例:

public enum EnumSingleton { INSTANCE; public void doSomething() { // 业务方法 } }

这种方式自动处理了序列化、反射等问题,是最简洁安全的单例实现。但缺点是它不够灵活(无法继承其他类)。

在Kotlin中,单例实现更加简单:

object KotlinSingleton { fun doSomething() { // 业务方法 } }

Kotlin的object关键字在底层就是使用静态内部类方式实现的单例。

← 返回列表