目录
单例模式
单例模式常见五种实现方式
饿汉式
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";
}
};
}
}