单例模式的理解

目录

单例模式

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.适合场景
需要 懒加载(延迟初始化),且希望 线程安全、代码简洁 的场景。
推荐作为日常开发中 "单例模式" 的首选方案,兼顾性能和安全性。
相关推荐
都叫我大帅哥10 分钟前
谁说数据库不能“直播”?用Debezium玩转实时数据流!
java
写bug写bug16 分钟前
彻底搞懂Spring Boot的系统监控机制
java·后端·spring
D_alyoo42 分钟前
02 流程流转
java
墨城之左1 小时前
低版本 IntelliJ IDEA 使用高版本 JDK 语言特性的问题
java·开发语言·intellij-idea·jdk21
帧栈1 小时前
开发避坑指南(23):Tomcat高版本URL特殊字符限制问题解决方案(RFC 7230 RFC 3986)
java·tomcat
不想当个程序员1 小时前
IDEA创建一个VUE项目
java·vue.js·intellij-idea
别来无恙1492 小时前
Java Web开发:Session与Cookie详细入门指南
java·开发语言
用户3721574261352 小时前
Java Excel转PDF方案分享
java