单例模式:饿汉式与懒汉式

一、成员变量(实例变量 + 静态变量)的初始化时机

Java 中成员变量的初始化时机分为实例变量静态变量两类,规则不同:

1. 实例变量(非 static 成员变量)的初始化时机

实例变量属于 “对象级” 变量,随对象创建而初始化,时机有 3 个:

  • 声明时直接赋值private int a = 10;(对象实例化时,构造器执行前完成);
  • 构造器中赋值this.a = 20;(声明时未赋值则在此初始化);
  • 初始化块({}){ a = 30; }(优先级介于声明赋值和构造器之间)。

核心:实例变量的初始化依赖对象实例化(必须调用构造器new对象),每创建一个对象,实例变量就重新初始化一次。

2. 静态变量(static 成员变量)的初始化时机

静态变量属于 “类级” 变量,随类加载而初始化,时机有 2 个:

  • 声明时直接赋值private static int b = 100;(类加载阶段的 “准备 - 初始化” 环节完成);
  • 静态初始化块(static {})static { b = 200; }(与声明赋值的顺序一致,谁在前先执行谁)。

核心:静态变量的初始化只在类加载时执行一次,与对象实例化无关(即使不创建对象,类加载后静态变量也已存在)。

二、饿汉式 vs 懒汉式(单例模式的两种核心实现)

两者的本质区别是单例实例的创建时机

1. 饿汉式:类加载时就创建实例(“饿”= 迫不及待)
public class Singleton {
    // 类加载时直接初始化实例(饿汉式核心)
    private static final Singleton INSTANCE = new Singleton();
    
    private Singleton() {}
    
    public static Singleton getInstance() {
        return INSTANCE; // 直接返回已创建的实例
    }
}
  • 时机:类加载阶段(程序启动 / 首次使用类时)就创建实例;
  • 特点:线程安全(类加载由 JVM 保证线程安全),但可能提前占用内存(即使暂时不用)。
2. 懒汉式:首次使用时才创建实例(“懒”= 拖延症)
public class Singleton {
    // 类加载时仅声明,不初始化
    private static Singleton INSTANCE;
    
    private Singleton() {}
    
    public static Singleton getInstance() {
        if (INSTANCE == null) { // 首次调用时才创建
            INSTANCE = new Singleton();
        }
        return INSTANCE;
    }
}
  • 时机:首次调用getInstance()方法时创建实例;
  • 特点:延迟加载(节省内存),但非线程安全(多线程同时调用可能创建多个实例),需加锁(如synchronized)优化。

三、为什么private static final Singleton INSTANCE = new Singleton();不会递归?

关键在于静态变量的初始化时机 + 类加载的 “初始化锁” 机制

1. 静态变量初始化是 “类加载阶段” 的操作,而非 “对象实例化阶段”
  • 当 JVM 加载Singleton类时,会先执行静态变量初始化INSTANCE = new Singleton()
  • 执行new Singleton()时,会调用私有构造器Singleton(),但此时构造器内没有再次创建INSTANCE的逻辑(构造器是空的);
  • 构造器执行完毕后,INSTANCE被赋值为唯一的实例,类加载完成。

整个过程是:类加载 → 静态变量初始化 → 调用构造器创建实例 → 构造器执行 → 实例赋值给静态变量,无递归。

2. 对比错误写法:private Singleton() { this.INSTANCE = new Singleton(); }

即使构造器是私有的,这种写法也会递归,原因是:

  • new Singleton()调用构造器时,构造器内执行this.INSTANCE = new Singleton(),这会再次调用构造器
  • 新的构造器又会执行new Singleton(),无限循环调用构造器,最终导致栈溢出(StackOverflowError);
  • 私有构造器仅限制 “外部类不能调用”,但类内部可以调用(包括构造器自己调用自己),因此私有性不影响递归的发生。
3. 核心区别:静态变量的 “唯一性” vs 实例变量的 “依赖性”
  • 饿汉式的INSTANCE静态变量,类加载时仅初始化一次,且构造器内没有再次创建实例的逻辑,因此不会递归;
  • 错误写法中的INSTANCE如果是实例变量(非 static),则每次创建对象都会初始化实例变量,而创建对象又依赖实例变量初始化,形成循环依赖,导致递归。

补充:懒汉式的单例模式为什么要加final

从语法上看,饿汉式去掉final也能运行(只要不主动修改INSTANCE),但:

  • 不加final会留下 “单例被破坏” 的隐患;static变量的作用域是类级别的,如果不加final,理论上类内部的其他方法可能意外修改INSTANCE的指向,破坏单例的唯一性。加上final后,INSTANCE的引用地址被固定,无法被重新赋值,从根本上杜绝了这种意外修改的可能。
  • 加上final是防御性编程的体现,符合单例模式 “唯一实例” 的核心约束。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值