单例模式对比:静态内部类 vs. 饿汉式

ops/2024/12/22 21:28:09/

单例模式是一种设计模式,旨在确保一个类只有一个实例,并提供全局访问点。Java 中有多种实现单例模式的方式,其中静态内部类实现和饿汉式实现是两种常见的方法。本文将对这两种单例模式进行详细对比,说明它们在延迟加载方面的区别,并解释为何某种实现方式会导致实例一上来就加载。

⚠️ 有一点需要大家先明确:JVM 对于类的加载是只有类 首次被使用 的时候才会被加载。但不同的单例模式实现方式会影响实例的加载时机。

1. 静态内部类实现单例模式
实现代码:
public class Singleton {// 私有构造函数,防止外部实例化private Singleton() {}// 静态内部类,持有 Singleton 的唯一实例private static class Holder {private static final Singleton INSTANCE = new Singleton();}// 提供对外的公共方法获取单例实例public static Singleton getInstance() {return Holder.INSTANCE;}
}
特点:
  • 延迟加载:静态内部类 Holder 只有在 getInstance() 方法被首次调用时才会被加载。JVM 确保 Holder 类在首次访问其静态成员 INSTANCE 时才会被初始化,从而实现了懒加载。这样可以避免在类加载时立即创建实例,节省资源。
  • 线程安全:静态内部类的加载是由 JVM 保证线程安全的,因此静态内部类单例模式天然地实现了线程安全。
2. 饿汉式实现单例模式
实现代码:
public class Singleton {// 立即创建单例实例private static final Singleton INSTANCE = new Singleton();// 私有构造函数,防止外部实例化private Singleton() {}// 提供对外的公共方法获取单例实例public static Singleton getInstance() {return INSTANCE;}
}
特点:
  • 饿汉式加载:在类加载时,静态成员变量 INSTANCE 被立即初始化。这意味着即使这个实例可能在类加载之后不会立即使用,它依然会在类加载时就被创建。这种方式没有延迟加载的机制,因此会在类加载时消耗资源。
  • 线程安全:由于 INSTANCE 在类加载时被初始化,类的加载过程是线程安全的,因此饿汉式单例模式天然是线程安全的。

对比分析

  1. 延迟加载
    • 静态内部类:支持延迟加载。静态内部类 Holder 只有在 getInstance() 方法首次被调用时才会被加载,这样避免了不必要的资源消耗。此处的延迟加载是因为静态内部类 Holder 的实例化是依赖于 getInstance() 方法的调用,而非类加载。
    • 饿汉式:不支持延迟加载。实例 INSTANCE 在类加载时就被创建,即使不立即使用,也会占用内存资源。这是因为在声明 INSTANCE 时就执行了 new Singleton(),导致类加载时即创建实例。
  2. 资源消耗
    • 静态内部类:只有在真正需要时才创建实例,因此更为高效,适合实例创建开销较大的情况。避免了在类加载时不必要的资源消耗。
    • 饿汉式:实例在类加载时就创建,即使没有实际使用,也会占用内存资源。这可能会导致内存消耗增加,尤其是在实例创建开销较大的情况下。
  3. 实现复杂度
    • 静态内部类:实现相对复杂,但支持延迟加载,能够有效节省资源。代码的维护和理解稍微复杂一些,但具有更好的资源管理能力。
    • 饿汉式:实现简单,但可能会导致不必要的内存消耗。其设计简单明了,但资源消耗可能会在不需要的情况下增加。
  4. 代码影响
    • 静态内部类:延迟加载是由于 Holder 类的静态成员 INSTANCE 只有在实际访问 getInstance() 方法时才会被初始化。JVM 确保 Holder 类的加载是按需进行的,这样实现了懒加载。
    • 饿汉式:实例立即加载是因为 INSTANCE 是在类加载时初始化的。private static final Singleton INSTANCE = new Singleton(); 这一行代码会在类加载时立即执行,导致实例在类加载时即创建。

总结

  • 静态内部类单例模式通过利用静态内部类的懒加载机制,实现了在实际需要时才创建实例的效果。这种方式在资源管理和延迟加载方面表现优异。
  • 饿汉式单例模式由于实例在类加载时就创建,不支持延迟加载,可能会在实例不需要时浪费资源。其实现简单,但可能导致不必要的内存消耗。

选择合适的单例模式实现方式应根据实际需求和资源情况来决定。如果需要高效的资源管理和懒加载,静态内部类实现是一个更优的选择。对于实现简单、对资源消耗不敏感的场景,饿汉式实现也是一种有效的方式。


http://www.ppmy.cn/ops/108931.html

相关文章

Linux网络——从《计算机网络》到网络编程

文章目录 从《计算机网络》到网络编程从计算机到计算机网络解决问题网络与计算机系统计算机网络的传输流程IP地址与MAC地址 从《计算机网络》到网络编程 科班的同学大多学过计算机网络,而非科班的同学也多多少少听说过一些 计算机网络体系十分繁杂且精妙&#xff…

Qt-QWidget的focusPolicy属性(20)

目录 描述 相关API 使用 描述 这里引入了焦点的概念,这个很重要,也是伴随后面介绍中的一个很重要的概念 拿魔兽世界来举例,如下我们在操作兵种的时候,需要先选中单位,然后才能对这些单位进行命令的下达 这一点在笔…

framebuffer帧缓存

framebuffer:帧缓冲,帧缓存 Linux内核为显示提供的一套应用程序接口。(驱动内核支持) framebuffer本质上是一块显示缓存,往显示缓存中写入特定格式的数据就意味着向屏幕输出内容。framebuffer驱动程序控制LCD显示设备&#xff0…

【专题】2024年8月医药行业报告合集汇总PDF分享(附原数据表)

原文链接:https://tecdat.cn/?p37621 在科技飞速发展的当今时代,医药行业作为关乎人类生命健康的重要领域,正处于前所未有的变革浪潮之中。数智医疗服务的崛起,为医疗模式带来了全新的转变,开启了医疗服务的新时代。…

C++11深度剖析

目录 🚀 前言:C11简介 一: 🔥 统一的列表初始化💫 2.1 {}初始化 二: 🔥 std::initializer_list 💫 2.1 std::initializer_list是什么类型💫 2.2 s…

黑马点评11——UV统计-HyperLogLog

文章目录 HyperLogLog的用法测试百万数据的统计 HyperLogLog的用法 简直就是天生用于UV统计的,太爽了! 测试百万数据的统计 /*** info memory* 2107168* 插入1000000条数据后,内存的变化* 2121552*/Testvoid testHyperLogLog(){String[] val…

iOS——APP启动流程

APP启动 APP启动主要分为两个阶段:pre-main和main之后,而APP的启动优化也主要是在这两个阶段进行的。 main之后的优化:1. 减少不必要的任务,2.必要的任务延迟执行,例如放在控制器界面等等。 APP启动的大致过程&#…

StorageSync数据缓存API

uni.setStorageSyncs参数:将 data 存储在本地缓存中指定的 key 中,会覆盖掉原来该 key 对应的内容,这是一个同步接口。 uni.setStorageSync函数里面写两个参数,分别是key和值,两个参数名称可以随便取,如果有同名的key,那么后面key的值会覆盖掉前面key的值…