目录
单例模式
1.饿汉式(线程安全)
java
package 并发的例子.单例模式;
// 饿汉式单例模式(天然线程安全,但不支持懒加载)
public class Singleton1 {
// 1. 静态成员变量:在类加载阶段(JVM层面)就完成实例化
// - static保证全局唯一一份,类加载时由JVM保证线程安全(仅初始化一次)
// - final修饰防止被意外修改,确保实例不可变
private static final Singleton1 INSTANCE = new Singleton1();
// 2. 私有构造方法:禁止外部通过new创建实例,保证单例唯一性
private Singleton1() {
}
// 3. 公开静态方法:提供全局访问点,直接返回已初始化的实例
public static Singleton1 getInstance() {
return INSTANCE;
}
// 测试:验证多次获取的是否为同一实例
public static void main(String[] args) {
Singleton1 instance1 = Singleton1.getInstance();
Singleton1 instance2 = Singleton1.getInstance();
Singleton1 instance3 = Singleton1.getInstance();
// 输出均为true,证明所有引用指向同一个实例
System.out.println(instance1 == instance2);
System.out.println(instance2 == instance3);
System.out.println(instance1 == instance3);
}
}
2.懒汉式(通过synchronized修饰获取实例的方法保证线程安全)
java
package 并发的例子.单例模式;
// 懒汉式(通过synchronized修饰获取实例的方法保证线程安全,但由于整个方法加锁,效率不高性能略差)
public class Singleton2 {
// 定义实例对象引用(仅声明,未创建实例,实现延迟初始化的基础)
private static Singleton2 instance;
// 私有构造方法,防止其他类通过new关键字创建实例,确保单例唯一性
private Singleton2() {
}
// 公开的静态方法,用于获取单例实例
// 使用synchronized修饰方法:保证多线程环境下,同一时间只有一个线程能进入方法,避免创建多个实例
public static synchronized Singleton2 getInstance() {
// 懒加载(延迟加载):只有当首次调用getInstance()时,才会创建实例对象,节省初始化资源
if (instance == null) {
instance = new Singleton2();
}
return instance;
}
public static void main(String[] args) {
// 测试单例模式:多次获取实例,验证是否为同一对象
Singleton2 s1 = Singleton2.getInstance();
Singleton2 s2 = Singleton2.getInstance();
Singleton2 s3 = Singleton2.getInstance();
// 通过hashCode判断是否为同一对象(同一对象的hashCode相同)
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
System.out.println(s3.hashCode());
}
}
3.双重校验锁的方式实现单例模式
java
package 并发的例子.单例模式;
// 用双重校验锁的方式实现单例模式
// 简单说就是:保证整个程序里只有这一个类的对象,而且线程安全、用到时才创建、效率还不错
public class Singleton3 {
// 存单例对象的地方,整个程序就这一个
// volatile关键字有两个超能力:
// 1. 防止"指令插队":创建对象的时候步骤必须是【分配内存→初始化对象→给instance赋值】,不能乱序
// 2. 保证"看得见":一个线程把对象创建好赋值给instance了,其他线程马上能看到,不会因为缓存犯傻
private static volatile Singleton3 instance;
// 把构造方法藏起来,不让外面用new创建对象
// 这样就只能通过我们写的getInstance方法来拿对象,保证只有一个
private Singleton3() {}
// 对外提供的拿单例对象的方法,全局就这一个入口
public static Singleton3 getInstance() {
// 第一次检查:先快速看看有没有对象
// 要是已经有了,直接返回,不用走后面的复杂流程,省时间
if (instance == null) {
// 加锁排队:多个线程同时到这的时候,只能一个一个来
// 锁的是整个类(Singleton3.class),保证全局就这一把锁
synchronized (Singleton3.class) {
// 第二次检查:进了锁之后再看一眼
// 防止多个线程都通过第一次检查后,进来重复创建对象
// 要是不检查,线程1创建完,线程2进来又创建,就不是单例了
if (instance == null) {
// 真正创建对象的地方
// 要是没加volatile,可能出现"对象还没初始化好,就把半成品给instance"的情况
// 加了volatile就保证步骤是【分配内存→初始化对象→给instance赋值】,稳稳的
instance = new Singleton3();
}
}
}
// 返回单例对象,不管哪个线程来拿,都是同一个
return instance;
}
}
4.静态内部类方式实现单例模式【推荐】
java
package 并发的例子.单例模式;
// 静态内部类方式实现单例模式【推荐】
// 特点:线程安全(依托类加载机制) + 懒加载(真正用到实例时才加载) + 简洁高效
public class Singleton4 {
// 1. 私有化构造方法
// 作用:禁止外部通过 new Singleton4() 创建对象,确保对象只能通过 getInstance() 获取
private Singleton4() {}
// 2. 静态内部类:SingletonHolder
// 特点:
// - 静态内部类不会随着外部类加载而加载,属于「懒加载」
private static class SingletonHolder {
// 3. 静态内部类中定义单例对象
// - final 保证实例不可变,一旦赋值无法修改
// - 类加载时创建实例,由 JVM 保证线程安全(多线程下不会重复创建)
private static final Singleton4 INSTANCE = new Singleton4();
}
// 4. 对外提供获取单例的方法
public static Singleton4 getInstance() {
// 调用此方法时,才会触发 SingletonHolder 的类加载
// 类加载过程中,JVM 会创建 INSTANCE,且保证全局唯一、线程安全
return SingletonHolder.INSTANCE;
}
public static void main(String[] args) {
Singleton4 s1 = Singleton4.getInstance();
Singleton4 s2 = Singleton4.getInstance();
Singleton4 s3 = Singleton4.getInstance();
// 通过hashCode判断是否为同一对象(同一对象的hashCode相同)
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
System.out.println(s3.hashCode());
}
}
java
1.为什么用静态内部类?
静态内部类 SingletonHolder 是 "懒汉":外部类 Singleton4 加载时,它不会跟着加载,真正调用 getInstance() 时才会加载,实现 "用的时候再创建"(懒加载)。
2.线程安全怎么保证?
JVM 加载类时,会保证 "同一类全局只加载一次",且加载过程是线程安全的(多线程同时调用 getInstance(),SingletonHolder 也只会加载一次)。
因此 INSTANCE 只会创建一次,天然线程安全。
3.对比其他单例的优势
比 "饿汉式" 懒:饿汉式类加载时就创建实例,静态内部类做到了 "用的时候才创建"。
比 "懒汉式(同步方法)" 高效:无需手动加锁,依托 JVM 类加载机制保证线程安全,性能更好。
4.适合场景
需要 懒加载(延迟初始化),且希望 线程安全、代码简洁 的场景。
推荐作为日常开发中 "单例模式" 的首选方案,兼顾性能和安全性。