设计模式学习笔记-----单例模式

发布于:2025-08-14 ⋅ 阅读:(23) ⋅ 点赞:(0)

目录

单例模式

单例模式常见五种实现方式

饿汉式

要点

枚举饿汉式

懒汉式

懒汉式-DCL

为什么要检查两次呢?

为什么要加volatile关键字?

懒汉式单例-内部类

懒汉式为什么不需要考虑多线程的问题呢?

jdk中有哪些地方体现了单例模式?

Runtime类 - 饿汉式

System类 - DCL

Collections类 - 懒汉式-内部类

Comparators类 - 枚举饿汉


单例模式

单例模式常见五种实现方式

饿汉式

package day01.pattern;

import java.io.Serializable;

// 1. 饿汉式单例模式实现类,实现Serializable接口,可支持对象序列化(需注意反序列化单例完整性)
public class Singleton1 implements Serializable {

    // 私有构造方法,防止外部通过new关键字创建实例,确保单例控制
    private Singleton1() {
        System.out.println("private Singleton1()");
    }

    // 静态的、最终的(不可修改)单例实例,类加载时就会创建该实例,这是饿汉式“饿”的体现(提前创建)
    private static final Singleton1 INSTANCE = new Singleton1();

    // 提供公共的静态方法,供外部获取单例实例,保证全局访问点唯一
    public static Singleton1 getInstance() {
        return INSTANCE;
    }

    // 类中其他静态方法示例,演示单例类也可包含其他功能逻辑
    public static void otherMethod() { 
        System.out.println("otherMethod()"); 
    }
}
要点

1、私有构造方法,防止外部通过new关键字创建实例,确保单例控制

2、静态的、最终的(不可修改)单例实例,类加载时就会创建该实例,这是饿汉式“饿”的体现(提前创建)

3、提供公共的静态方法,供外部获取单例实例,保证全局访问点唯一

4、反射破坏单例,预防:

package day01.pattern;

import java.io.Serializable;

// 1. 饿汉式单例模式实现类,实现Serializable接口,支持对象序列化(反序列化单例完整性需额外处理)
public class Singleton1 implements Serializable {

    // 静态的、最终的(不可修改)单例实例,类加载阶段就会创建,体现“饿汉”提前初始化特点
    private static final Singleton1 INSTANCE = new Singleton1();

    // 私有构造方法,阻止外部直接new创建实例,是单例控制核心
    private Singleton1() {
        // 防御性判断:若实例已存在(非null),抛出运行时异常,避免通过反射等手段重复创建实例
        if (INSTANCE != null) {
            throw new RuntimeException("单例对象不能重复创建");
        }
        System.out.println("private Singleton1()");
    }

    // 公共静态方法,提供全局唯一访问点,让外部获取单例实例
    public static Singleton1 getInstance() {
        return INSTANCE;
    }

    // 单例类中其他静态方法示例,展示单例类可承载额外功能逻辑
    public static void otherMethod() { 
        System.out.println("otherMethod()"); 
    }
}

5、反序列化破坏单例,实现Serializable接口;预防:

// 1. 饿汉式单例模式实现类
// 实现 Serializable 接口,允许该类对象被序列化和反序列化
public class Singleton1 implements Serializable {

    // 静态 final 常量,类加载阶段直接创建单例实例
    // 饿汉式的核心:类加载时立即初始化实例,线程安全由 JVM 类加载机制保证
    private static final Singleton1 INSTANCE = new Singleton1();

    // 私有构造方法,禁止外部直接实例化
    private Singleton1() {
        // 防御性检查:防止通过反射暴力创建新实例
        // 当 INSTANCE 已初始化后(非 null),再次调用构造方法直接抛异常
        if (INSTANCE != null) {
            throw new RuntimeException("单例对象不能重复创建");
        }
        System.out.println("private Singleton1()");
    }

    // 全局访问点:提供给外部获取单例实例的唯一方法
    public static Singleton1 getInstance() {
        return INSTANCE;
    }

    // 单例类中其他静态方法示例(非单例核心逻辑,仅演示功能扩展)
    public static void otherMethod() {
        System.out.println("otherMethod()");
    }

    // 反序列化核心方法:防止反序列化时创建新对象
    // 当 JVM 反序列化对象时,会调用此方法返回实例
    // 直接返回预先创建的单例,保证反序列化前后是同一个实例
    public Object readResolve() {
        return INSTANCE;
    }
}

6、Unsafe破坏单例;

枚举饿汉式

package day01.pattern;

// 2. 枚举饿汉式单例模式实现类
public enum Singleton2 {

    // 枚举常量,代表单例的唯一实例,枚举类型会保证该实例全局唯一且仅创建一次
    INSTANCE;

    // 私有构造方法,枚举的构造方法默认就是私有的(这里显式声明强化语义),防止外部创建实例
    // 枚举常量初始化时会调用此构造方法
    private Singleton2() {
        System.out.println("private Singleton2()");
    }

    // 重写 toString 方法,自定义枚举实例的字符串表示形式,便于调试等场景查看实例信息
    @Override
    public String toString() {
        // 返回 全类名@哈希码十六进制形式 ,类似常见对象默认 toString 格式
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

    // 提供公共静态方法,获取单例实例,虽然枚举可直接通过 Singleton2.INSTANCE 使用,但保留类似常规单例的 getInstance 方法风格
    public static Singleton2 getInstance() {
        return INSTANCE;
    }

    // 枚举类中其他静态方法示例,展示枚举单例也可包含额外功能逻辑
    public static void otherMethod() {
        System.out.println("otherMethod()");
    }
}

反射和反序列号无法破坏单例

懒汉式

package day01.pattern;

import java.io.Serializable;

// 3. 懒汉式单例模式实现类(基础版,存在线程安全等问题,需结合场景完善)
public class Singleton3 implements Serializable {

    // 私有构造方法,禁止外部通过new关键字创建实例,是实现单例的基础
    private Singleton3() {
        System.out.println("private Singleton3()");
    }

    // 静态成员变量,用于存储单例实例,初始为null,体现“懒汉”延迟创建的特点(用到时才创建)
    private static Singleton3 INSTANCE = null;

    // 公共的静态方法,提供全局访问点,用于获取单例实例
    public static Singleton3 getInstance() {
        // 判断实例是否未创建(为null),若未创建则进入创建逻辑
        if (INSTANCE == null) {
            // 这里存在线程安全问题!多线程环境下,可能多个线程同时进入该判断,导致创建多个实例
            // 实际生产需结合同步机制(如synchronized、双重检查锁等)处理
            INSTANCE = new Singleton3();
        }
        // 返回单例实例,若已创建则直接返回已有的实例
        return INSTANCE;
    }

    // 类中其他静态方法示例,演示单例类也可包含常规功能逻辑
    public static void otherMethod() {
        System.out.println("otherMethod()");
    }
}

存在线程安全问题!多线程环境下,可能多个线程同时进入该判断,导致创建多个实例;预防:

package day01.pattern;

import java.io.Serializable;

// 3. 懒汉式单例模式实现类(线程安全基础版,通过 synchronized 保证多线程下实例唯一)
// 实现 Serializable 接口,支持对象序列化(需注意反序列化单例完整性,代码未完善该部分)
public class Singleton3 implements Serializable {

    // 私有构造方法,禁止外部通过 new 关键字创建实例,确保单例控制
    private Singleton3() {
        System.out.println("private Singleton3()");
    }

    // 静态成员变量,存储单例实例,初始为 null,体现“懒汉”延迟创建的特点(用到时才初始化)
    private static Singleton3 INSTANCE = null;

    // 公共的静态方法,提供全局访问点,获取单例实例
    // synchronized 关键字修饰方法,保证多线程环境下,同一时间只有一个线程能进入该方法,避免创建多个实例
    public static synchronized Singleton3 getInstance() {
        // 判断实例是否未创建(为 null),若未创建则执行创建逻辑
        if (INSTANCE == null) {
            INSTANCE = new Singleton3();
        }
        // 返回单例实例,若已创建则直接返回现有实例
        return INSTANCE;
    }

    // 类中其他静态方法示例,展示单例类可扩展其他功能逻辑
    public static void otherMethod() {
        System.out.println("otherMethod()");
    }
}

问题:只需要在首次创建时得到锁的保护,其他时候不需要。锁的粒度过大;解法:

懒汉式-DCL

package day01.pattern;

import java.io.Serializable;

// 4. 懒汉式单例模式 - 双重检查锁(DCL)实现,线程安全且高效
// 实现 Serializable 接口,支持对象序列化(需注意反序列化单例完整性,代码未完善该部分)
public class Singleton4 implements Serializable {

    // 私有构造方法,禁止外部通过 new 关键字创建实例,确保单例控制
    private Singleton4() {
        System.out.println("private Singleton4()");
    }

    // 静态成员变量,存储单例实例,初始为 null,体现“懒汉”延迟创建的特点
    // volatile 关键字修饰,作用:
    // 1. 保证多线程环境下变量的可见性,一个线程修改了 INSTANCE 的值,其他线程能立即看到最新值
    // 2. 禁止指令重排序,避免 INSTANCE = new Singleton4() 执行过程中,指令重排导致的线程安全问题
    private static volatile Singleton4 INSTANCE = null;

    // 公共的静态方法,提供全局访问点,获取单例实例,采用双重检查锁(DCL)优化线程安全与效率
    public static Singleton4 getInstance() {
        // 第一次检查:快速判断实例是否已创建,若已创建直接返回,避免每次都进入同步代码块,提升效率
        if (INSTANCE == null) {
            // 同步代码块,锁定当前类的 Class 对象,保证同一时间只有一个线程能进入该代码块
            synchronized (Singleton4.class) {
                // 第二次检查:进入同步代码块后,再次判断实例是否为 null
                // 防止多线程环境下,多个线程突破第一次检查后,重复创建实例
                if (INSTANCE == null) {
                    INSTANCE = new Singleton4();
                }
            }
        }
        // 返回单例实例,若已创建则直接返回现有实例
        return INSTANCE;
    }

    // 类中其他静态方法示例,展示单例类可扩展其他功能逻辑
    public static void otherMethod() {
        System.out.println("otherMethod()");
    }
}
为什么要检查两次呢?


内层检查是防止挂起阻塞的线程再次创建单例对象,导致单例被破坏

为什么要加volatile关键字?


    1. 保证多线程环境下变量的可见性,一个线程修改了 INSTANCE 的值,其他线程能立即看到最新值
    2. 禁止指令重排序,避免 INSTANCE = new Singleton4() 执行过程中,指令重排导致的线程安全问题

创建对象,构造,赋值。

如果指令没有前后因果关系,cpu会对指令重排序以优化性能

创建对象,构造有因果关系,构造,赋值没有因果关系,cpu会对构造,赋值进行指令的重排序,变成先赋值,在构造。多线程下有问题!

外层的if快没有受到锁的保护,

构造方法还没执行,拿到的只是一个没有完整构造的一个半成品对象,使用时会出很多问题

volatile原理: 内存屏障防止指令重排

懒汉式单例-内部类

package day01.pattern;

import java.io.Serializable;

// 5. 懒汉式单例模式 - 静态内部类实现,线程安全且延迟初始化
// 实现 Serializable 接口,支持对象序列化(需注意反序列化单例完整性,代码未完善该部分)
public class Singleton5 implements Serializable {

    // 私有构造方法,禁止外部通过 new 关键字创建实例,确保单例控制
    private Singleton5() {
        System.out.println("private Singleton5()");
    }

    // 静态内部类,用于持有单例实例
    // 特点:
    // 1. 内部类不会随着外部类的加载而加载,只有当调用到内部类的静态成员时才会加载
    // 2. 类加载过程由 JVM 保证线程安全,因此内部类的静态成员初始化是线程安全的
    private static class Holder {
        // 静态内部类的静态成员,存储单例实例,在内部类加载时创建实例
        static Singleton5 INSTANCE = new Singleton5();
    }

    // 公共的静态方法,提供全局访问点,获取单例实例
    public static Singleton5 getInstance() {
        // 返回静态内部类中持有的单例实例
        // 调用此方法时,才会触发 Holder 类的加载,进而创建 Singleton5 实例,实现延迟初始化
        return Holder.INSTANCE;
    }

    // 类中其他静态方法示例,展示单例类可扩展其他功能逻辑
    public static void otherMethod() {
        System.out.println("otherMethod()");
    }
}

没有调用getInstance方法就不会触发Holder类加载

懒汉式为什么不需要考虑多线程的问题呢?

new Singleton1()是赋值给了静态代码快(static),给静态变量赋值会放到静态代码快中执行的,静态代码块中的线程安全不用考虑,由类加载器保障它的线程安全性。

jdk中有哪些地方体现了单例模式?

Runtime类 - 饿汉式

public class Runtime {
    // 静态 final 成员,类加载时创建唯一实例,饿汉式单例的体现
    private static final Runtime currentRuntime = new Runtime();

    // 用于存储版本信息的静态成员(示例补充,实际 JDK 中会有更复杂的版本处理逻辑 )
    private static Version version;

    // 私有构造方法,禁止外部实例化,保证单例
    private Runtime() {}

    // 公共静态方法,提供全局访问点,获取唯一的 Runtime 实例
    public static Runtime getRuntime() {
        return currentRuntime;
    }

    // 以下补充示例方法,模拟 Runtime 实际具备的功能(如终止 JVM 等,实际逻辑更复杂 )
    // 终止当前运行的 Java 虚拟机,触发关闭序列
    public void exit(int status) {
        // 实际 JDK 中会调用底层系统相关方法执行退出逻辑,这里简化演示
        System.out.println("Java VM exiting with status: " + status);
        // 真实场景会有与 JVM 交互的 native 方法或复杂关闭流程,如通知注册的关闭钩子等
        System.exit(status); 
    }

    // 获取 Java 运行时版本信息(示例简化,实际会读取真实版本数据 )
    public static Version getVersion() {
        if (version == null) {
            // 实际会从系统属性、JVM 内部获取版本信息并初始化,这里模拟创建
            version = new Version("1.8.0_xxx"); 
        }
        return version;
    }

    // 内部类,模拟版本信息封装(实际 JDK 中有具体的 Version 类定义 )
    public static class Version {
        private String versionStr;
        public Version(String versionStr) {
            this.versionStr = versionStr;
        }
        @Override
        public String toString() {
            return versionStr;
        }
    }
}

System类 - DCL

import java.io.PrintStream;
import sun.misc.SharedSecrets;
import java.io.Console;

public class System {
    // 用于存储错误输出流
    private static volatile PrintStream err;

    // 用于存储 Console 实例(volatile 保证多线程可见性)
    private static volatile Console cons;

    // 设置错误输出流的方法
    public static void setErr(PrintStream err) {
        checkIO(); 
        setErr0(err); 
    }

    // 实际执行设置错误输出流的 native 方法(JVM 内部实现,这里模拟声明 )
    private static native void setErr0(PrintStream err);

    // 检查 IO 相关权限等(实际 JDK 中有更复杂的安全检查逻辑 )
    private static void checkIO() {
        // 模拟简单检查:比如判断是否有修改权限(实际会涉及安全管理器等 )
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(new RuntimePermission("setIO"));
        }
    }

    // 获取 Console 实例的方法,采用双重检查锁(DCL)保证线程安全与懒加载
    public static Console console() {
        Console c;
        // 第一次检查:快速判断,避免每次进入同步块
        if ((c = cons) == null) { 
            synchronized (System.class) { 
                // 第二次检查:防止多线程突破第一次检查后重复创建
                if ((c = cons) == null) { 
                    // 通过 SharedSecrets 获取 Java IO 访问权限,进而获取 Console
                    cons = c = SharedSecrets.getJavaIOAccess().console(); 
                }
            }
        }
        return c;
    }

    // 以下为补充的 SecurityManager 相关模拟逻辑(实际 JDK 中在 java.lang 包下 )
    private static SecurityManager security = null;
    public static SecurityManager getSecurityManager() {
        return security;
    }
    public static void setSecurityManager(SecurityManager s) {
        security = s;
    }
}

// 模拟 java.io.Console 类(实际 JDK 中为抽象类,有具体实现 )
abstract class Console {
    // 实际会有读取行、读取密码等方法,这里简化
    public abstract String readLine();
}

// 模拟 sun.misc.SharedSecrets 类(实际为 JDK 内部类,提供访问内部 API 的通道 )
class SharedSecrets {
    private static JavaIOAccess javaIOAccess;
    public static JavaIOAccess getJavaIOAccess() {
        return javaIOAccess;
    }
    public static void setJavaIOAccess(JavaIOAccess access) {
        javaIOAccess = access;
    }
}

// 模拟 JavaIOAccess 类,用于获取 Console 等资源
class JavaIOAccess {
    public Console console() {
        // 实际会根据系统环境创建/获取 Console 实例,这里简化返回 null 或模拟实现
        return new Console() {
            @Override
            public String readLine() {
                return "Mock Console Input";
            }
        };
    }
}

Collections类 - 懒汉式-内部类

Comparators类 - 枚举饿汉


网站公告

今日签到

点亮在社区的每一天
去签到