
本文详解单线程环境下 singleton 模式的本质要求:唯一实例 + 禁止外部构造,指出仅靠 `getinstance()` 重赋值无法保证单例性,并对比分析两种写法的合规性。
Singleton(单例)模式的核心契约是:在整个 JVM 生命周期中,该类有且仅有一个实例存在,并且该实例的创建与访问必须受控。这一约束在单线程环境中虽无需考虑并发安全,但依然严格要求两点:
- 私有化构造器(private constructor)——彻底阻止外部通过 new 关键字创建新实例;
- 全局唯一访问入口(如静态 getInstance() 方法)——确保所有获取实例的路径都指向同一对象。
我们来逐个分析您提供的两个实现:
✅ 第一种写法(合规的单例):
public class Singleton {
private static Singleton obj;
private Singleton() {} // ✅ 私有构造器:外部无法 new
public static Singleton getInstance() {
if (obj == null) {
obj = new Singleton(); // ✅ 唯一合法的实例创建点
}
return obj;
}
}该实现满足单例全部前提:构造器私有 → 无外部实例化可能;getInstance() 是唯一获取途径 → 所有调用均返回同一 obj 引用。在单线程下,它能稳定保证全局唯一实例。
❌ 第二种写法(非单例,本质失效):
public class SingleTon {
static SingleTon s;
SingleTon getInstance() { // ⚠️ 非静态方法,设计混乱
if (s == null) {
s = new SingleTon(); // ✅ 第一次创建
}
return s;
}
public static void main(String[] args) {
SingleTon s1 = new SingleTon(); // ❌ 公共默认构造器允许直接 new!
s1 = s1.getInstance(); // ✅ 此时 s1 指向共享实例 s
SingleTon s2 = new SingleTon(); // ❌ 再次 new —— 创建了第二个独立实例!
s2 = s2.getInstance(); // ✅ s2 也指向 s,但原 s2 对象已丢失引用(仍存在于内存)
}
}问题关键在于:未声明 private SingleTon() → 编译器自动注入 public SingleTon() → 任何代码均可调用 new SingleTon() 创建任意数量实例。即使后续 s1 = s1.getInstance() 将引用重定向到共享对象 s,最初的 new SingleTon() 实例依然真实存在、占用内存、且完全脱离单例管理。这直接违背“有且仅有一个实例”的根本定义。
此外,该实现还存在设计缺陷:
- getInstance() 是实例方法而非 static,导致调用前必须先创建一个临时实例(如 new SingleTon().getInstance()),逻辑冗余且易引发误解;
- 静态字段 s 与实例方法混用,职责不清,破坏封装性。
? 总结与最佳实践:
- 单例的“单”体现在实例数量为一,而非“变量引用指向同一对象”;
- private 构造器是强制性前提,不可省略或妥协;
- getInstance() 必须为 public static 方法,确保统一、无依赖的访问入口;
- 单线程下推荐使用饿汉式(类加载时初始化)或懒汉式(如题中第一种),二者均需私有构造器支撑。
正确实现示例(懒汉式,单线程安全):
public class Singleton {
private static Singleton instance;
private Singleton() {} // ? 核心防线:禁止外部构造
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}牢记:没有私有构造器的“单例”,只是披着单例外衣的普通类。










