云计算百科
云计算领域专业知识百科平台

单例模式笔记

免费版Java学习笔记(38w字)链接:https://www.yuque.com/aoyouaoyou/sgcqr8 免费版Java面试题(28w字)链接:https://www.yuque.com/aoyouaoyou/wh3hto 完整版可在小红书搜索:遨游qk0

单例模式是创建型设计模式中最常用的一种,核心是保证某个类在程序运行期间仅有一个实例,并提供全局统一的访问入口。适用于日志工具、配置管理、数据库连接池等需避免重复创建的场景,也是面试高频考点。

此处为语雀内容卡片,点击链接查看:https://www.yuque.com/aoyouaoyou/pw1w5m/grpkf5br1h3qc2yr

代码位置:

一、单例模式定义与目标

1. 定义

确保一个类在整个应用生命周期中只有一个实例,且该实例能被全局访问,避免重复创建对象导致的资源浪费(如内存、CPU)。

2. 目标

  • 限制实例数量:仅允许创建一个实例;
  • 全局访问:提供统一的静态方法供外部获取实例;
  • 线程安全:多线程环境下仍能保证实例唯一性。

二、单例模式6种实现方式

1. 饿汉式(预加载)

核心原理

类加载时直接初始化静态实例,由JVM保证线程安全(类加载过程是单线程的)。

代码实现

/**
* 饿汉式单例:类加载时创建实例,不支持懒加载
*/
public class CityServiceCenter {
// 1. 私有构造方法:禁止外部通过new创建实例
private CityServiceCenter() {}

// 2. 私有静态实例:类加载时初始化(预加载)
private static final CityServiceCenter INSTANCE = new CityServiceCenter();

// 3. 全局访问方法:返回唯一实例
public static CityServiceCenter getInstance() {
return INSTANCE;
}

// 示例业务方法
public void handleBusiness(String userName) {
System.out.println(userName + " 正在市政服务中心办理业务");
}
}
┌─────────────────────────────────────────────────────────────┐
│ CityServiceCenter │
├─────────────────────────────────────────────────────────────┤
│ – INSTANCE: CityServiceCenter = new CityServiceCenter() │
├─────────────────────────────────────────────────────────────┤
│ – CityServiceCenter() │
│ + getInstance(): CityServiceCenter │
│ + handleBusiness(String): void │
└─────────────────────────────────────────────────────────────┘

时序流程:
Client → getInstance() → INSTANCE
类加载时已初始化

特点

  • 优点:实现简单、线程安全、获取实例速度快;
  • 缺点:不支持懒加载(类加载时就创建实例),若实例占用资源大且长期未使用,会造成内存浪费;
  • 适用场景:实例体积小、启动时必用的场景(如配置类)。

2. 懒汉式(懒加载-线程不安全)

核心原理

仅当外部调用getInstance()方法时才创建实例,支持懒加载,但多线程环境下存在线程安全问题。

代码实现

/**
* 懒汉式单例:懒加载,线程不安全(多线程下可能创建多个实例)
*/
public class WaterSupplySystem {
// 1. 私有构造方法
private WaterSupplySystem() {}

// 2. 私有静态实例:初始为null,不预加载
private static WaterSupplySystem instance;

// 3. 全局访问方法:调用时才创建实例
public static WaterSupplySystem getInstance() {
// 未加锁:多线程可能同时进入判断,创建多个实例
if (instance == null) {
instance = new WaterSupplySystem();
}
return instance;
}

// 示例业务方法
public void supplyWater(String area) {
System.out.println("为 " + area + " 区域供水");
}
}
┌─────────────────────────────────────────────────────────────┐
│ WaterSupplySystem │
├─────────────────────────────────────────────────────────────┤
│ – instance: WaterSupplySystem = null │
├─────────────────────────────────────────────────────────────┤
│ – WaterSupplySystem() │
│ + getInstance(): WaterSupplySystem │
│ + supplyWater(String): void │
└─────────────────────────────────────────────────────────────┘

时序流程:
Client → getInstance() → if(instance==null) → new instance
多线程下可能创建多个实例

特点

  • 优点:支持懒加载,节省内存;
  • 缺点:线程不安全(多线程并发调用时可能创建多个实例);
  • 适用场景:单线程环境,或不关注线程安全的简单场景(不推荐生产使用)。

3. 懒汉式(懒加载-线程安全)

核心原理

在getInstance()方法上添加synchronized同步锁,强制多线程串行执行,避免并发创建实例。

代码实现

/**
* 懒汉式单例:懒加载+同步锁,线程安全但性能低
*/
public class PowerGridControl {
// 1. 私有构造方法
private PowerGridControl() {}

// 2. 私有静态实例
private static PowerGridControl instance;

// 3. 同步方法:保证多线程串行调用
public static synchronized PowerGridControl getInstance() {
if (instance == null) {
instance = new PowerGridControl();
}
return instance;
}

// 示例业务方法
public void adjustPower(String region) {
System.out.println("调整 " + region + " 区域电网负荷");
}
}
┌─────────────────────────────────────────────────────────────┐
│ PowerGridControl │
├─────────────────────────────────────────────────────────────┤
│ – instance: PowerGridControl = null │
├─────────────────────────────────────────────────────────────┤
│ – PowerGridControl() │
│ + getInstance(): synchronized PowerGridControl │
│ + adjustPower(String): void │
└─────────────────────────────────────────────────────────────┘

时序流程:
Thread1 → getInstance() → [LOCK] → if(instance==null) → new instance
Thread2 → 等待锁释放 → [LOCK] → 直接返回instance

特点

  • 优点:线程安全、支持懒加载;
  • 缺点:性能瓶颈(同步锁导致多线程串行执行,并发度为1);
  • 适用场景:低并发场景,对性能要求不高的业务。

4. 双重校验锁(DCL:Double-Check Locking)

核心原理

结合“懒加载+同步代码块+volatile关键字”,既保证线程安全,又提升并发性能(仅初始化时加锁,后续获取实例无需锁)。

代码实现

/**
* 双重校验锁单例:懒加载+高并发+线程安全(推荐生产使用)
*/
public class TrafficControlSystem {
// 1. 私有构造方法
private TrafficControlSystem() {}

// 2. 私有静态实例:volatile关键字禁止指令重排序
private static volatile TrafficControlSystem instance;

// 3. 双重校验+同步代码块
public static TrafficControlSystem getInstance() {
// 第一次校验:避免已创建实例后仍进入同步代码块(提升性能)
if (instance == null) {
// 同步锁:锁定类对象,保证初始化时单线程执行
synchronized (TrafficControlSystem.class) {
// 第二次校验:防止多线程同时等待锁后重复创建实例
if (instance == null) {
instance = new TrafficControlSystem();
}
}
}
return instance;
}

// 示例业务方法
public void controlTrafficLight(String intersection) {
System.out.println("控制 " + intersection + " 路口红绿灯");
}
}
┌─────────────────────────────────────────────────────────────┐
│ TrafficControlSystem │
├─────────────────────────────────────────────────────────────┤
│ – instance: volatile TrafficControlSystem = null │
├─────────────────────────────────────────────────────────────┤
│ – TrafficControlSystem() │
│ + getInstance(): TrafficControlSystem │
│ + controlTrafficLight(String): void │
└─────────────────────────────────────────────────────────────┘

时序流程:
Thread1 → getInstance() → if(instance==null) → [LOCK] → if(instance==null) → new instance
Thread2 → getInstance() → if(instance!=null) → 直接返回instance (无锁)

关键说明:volatile关键字的作用

instance = new TrafficControlSystem() 并非原子操作,JVM会拆分为3步:

  • 分配内存空间;
  • 初始化实例;
  • 将instance指向内存空间。
  • 若无volatile,JVM可能指令重排序(执行顺序变为1→3→2),导致线程A未完成初始化时,线程B读取到非null但未初始化的实例,引发空指针异常。volatile可禁止指令重排序,保证实例初始化完整。

    特点

    • 优点:线程安全、支持懒加载、高并发性能好;
    • 缺点:实现稍复杂,需理解volatile关键字作用;
    • 适用场景:高并发生产环境(最常用的实现方式)。

    5. 静态内部类(推荐)

    核心原理

    利用静态内部类的特性:外部类加载时不会加载内部类,仅当调用getInstance()时才加载内部类并初始化实例,由JVM保证线程安全。

    代码实现

    /**
    * 静态内部类单例:懒加载+线程安全+实现简洁(推荐生产使用)
    */
    public class EnvironmentalMonitor {
    // 1. 私有构造方法
    private EnvironmentalMonitor() {}

    // 2. 静态内部类:仅当被调用时才加载
    private static class MonitorHolder {
    // 内部类中初始化外部类实例
    private static final EnvironmentalMonitor INSTANCE = new EnvironmentalMonitor();
    }

    // 3. 全局访问方法:触发内部类加载
    public static EnvironmentalMonitor getInstance() {
    return MonitorHolder.INSTANCE;
    }

    // 示例业务方法
    public void detectAirQuality(String city) {
    System.out.println("检测 " + city + " 空气质量:优");
    }
    }
    ┌─────────────────────────────────────────────────────────────┐
    │ EnvironmentalMonitor │
    ├─────────────────────────────────────────────────────────────┤
    │ – EnvironmentalMonitor() │
    ├─────────────────────────────────────────────────────────────┤
    │ + getInstance(): EnvironmentalMonitor │
    │ + detectAirQuality(String): void │
    │ ┌─────────────────────────────────────────────────────────┐ │
    │ │ static class MonitorHolder │ │
    │ ├─────────────────────────────────────────────────────────┤ │
    │ │ – INSTANCE: EnvironmentalMonitor = new instance │ │
    │ └─────────────────────────────────────────────────────────┘ │
    └─────────────────────────────────────────────────────────────┘

    时序流程:
    Client → getInstance() → MonitorHolder.INSTANCE
    首次调用时加载内部类并初始化实例

    特点

    • 优点:线程安全(JVM加载内部类时单线程)、支持懒加载、实现简洁、无性能损耗;
    • 缺点:无法通过反射防止实例破坏(需额外处理);
    • 适用场景:大多数生产环境,兼顾简洁性与性能。

    6. 枚举单例(《Effective Java》推荐)

    此处为语雀内容卡片,点击链接查看:https://www.yuque.com/aoyouaoyou/pbz18g/hx9h4u1ufedbkn4a

    核心原理

    利用Java枚举的特性:枚举实例默认线程安全、仅初始化一次,且天然防止反射和序列化破坏单例。

    代码实现

    /**
    * 枚举单例:线程安全+防反射+防序列化(最稳定的实现方式)
    */
    public enum WeatherForecastSystem {
    // 唯一枚举实例(等价于单例实例)
    INSTANCE;

    // 枚举类可定义属性和方法
    private String latestForecast;

    // 示例业务方法
    public void updateForecast(String forecast) {
    this.latestForecast = forecast;
    System.out.println("更新天气预报:" + forecast);
    }

    public String getLatestForecast() {
    return latestForecast;
    }
    }
    ┌─────────────────────────────────────────────────────────────┐
    │ WeatherForecastSystem (enum) │
    ├─────────────────────────────────────────────────────────────┤
    │ INSTANCE │
    │ – latestForecast: String │
    ├─────────────────────────────────────────────────────────────┤
    │ + updateForecast(String): void │
    │ + getLatestForecast(): String │
    └─────────────────────────────────────────────────────────────┘

    使用方式:
    WeatherForecastSystem.INSTANCE.updateForecast("晴")

    特点

    • 优点:
  • 线程安全:枚举实例由JVM保证仅初始化一次;
  • 防反射:Java禁止通过反射创建枚举实例;
  • 防序列化:枚举序列化时仅存储名称,反序列化时通过名称获取原有实例,不会创建新实例;
  • 实现极简;
    • 缺点:不支持懒加载(枚举类加载时初始化实例);
    • 适用场景:对单例稳定性要求极高的场景(如核心配置、支付系统)。

    三、单例模式的破坏与解决方案

    1. 反射破坏及防护

    问题描述

    通过Java反射机制,可强制访问私有构造方法,创建多个实例。

    防护方案(以静态内部类为例)

    在构造方法中添加判断,若实例已存在则抛出异常:

    private EnvironmentalMonitor() {
    // 防护反射破坏:若实例已存在,抛出异常
    if (MonitorHolder.INSTANCE != null) {
    throw new IllegalStateException("单例类禁止重复创建实例");
    }
    }

    2. 序列化破坏及防护

    问题描述

    实现Serializable接口的单例类,序列化后再反序列化,会创建新实例(破坏单例)。

    防护方案

    添加readResolve()方法,指定反序列化时返回已有的单例实例:

    public class EnvironmentalMonitor implements Serializable {
    // 原有代码不变…

    // 反序列化时调用,返回单例实例
    private Object readResolve() {
    return MonitorHolder.INSTANCE;
    }
    }
    package com.ao.a_singleton.demo;

    import java.io.Serializable;

    /**
    * 静态内部类单例:懒加载+线程安全+实现简洁(推荐生产使用)
    */
    public class EnvironmentalMonitor implements Serializable {
    // 1. 私有构造方法
    //private EnvironmentalMonitor() {}

    private EnvironmentalMonitor() {
    // 防护反射破坏:若实例已存在,抛出异常
    if (MonitorHolder.INSTANCE != null) {
    throw new IllegalStateException("单例类禁止重复创建实例");
    }
    }

    // 2. 静态内部类:仅当被调用时才加载
    private static class MonitorHolder {
    // 内部类中初始化外部类实例
    private static final EnvironmentalMonitor INSTANCE = new EnvironmentalMonitor();
    }

    // 3. 全局访问方法:触发内部类加载
    public static EnvironmentalMonitor getInstance() {
    return MonitorHolder.INSTANCE;
    }

    // 示例业务方法
    public void detectAirQuality(String city) {
    System.out.println("检测 " + city + " 空气质量:优");
    }

    // 反序列化时调用,返回单例实例
    private Object readResolve() {
    return MonitorHolder.INSTANCE;
    }
    }

    ┌─────────────────────────────────────────────────────────────┐
    │ EnvironmentalMonitor │
    │ implements Serializable │
    ├─────────────────────────────────────────────────────────────┤
    │ – EnvironmentalMonitor() │
    │ ↳ 反射防护: if(instance存在) throw Exception │
    ├─────────────────────────────────────────────────────────────┤
    │ + getInstance(): EnvironmentalMonitor │
    │ – readResolve(): Object (序列化防护) │
    │ ┌─────────────────────────────────────────────────────────┐ │
    │ │ static class MonitorHolder │ │
    │ ├─────────────────────────────────────────────────────────┤ │
    │ │ – INSTANCE: EnvironmentalMonitor │ │
    │ └─────────────────────────────────────────────────────────┘ │
    └─────────────────────────────────────────────────────────────┘

    四、6种实现方式对比总结

    实现方式

    线程安全

    懒加载

    防反射

    防序列化

    适用场景

    饿汉式

    实例体积小、启动必用

    懒汉式(无锁)

    单线程测试场景

    懒汉式(同步方法)

    低并发、对性能无要求

    双重校验锁

    高并发生产环境

    静态内部类

    大多数生产环境(推荐)

    枚举单例

    高稳定性核心业务(推荐)

    五、使用建议

  • 优先选择静态内部类或枚举单例:静态内部类兼顾懒加载与性能,枚举单例兼顾稳定性与简洁性;
  • 避免使用懒汉式(无锁/同步方法):无锁线程不安全,同步方法性能差;
  • 饿汉式仅用于小实例:避免大实例预加载造成内存浪费;
  • 核心业务用枚举单例:防反射、防序列化,稳定性最高;
  • 避免滥用单例:仅用于需唯一实例的场景,否则会导致代码耦合度升高。
  • 赞(0)
    未经允许不得转载:网硕互联帮助中心 » 单例模式笔记
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!