单例模式

308 阅读9分钟

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

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

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

代码位置:

一、单例模式定义与目标

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步:

  1. 分配内存空间;
  2. 初始化实例;
  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         │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

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

特点

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

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

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

核心原理

利用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("晴")

特点

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

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

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种实现方式对比总结

实现方式线程安全懒加载防反射防序列化适用场景
饿汉式实例体积小、启动必用
懒汉式(无锁)单线程测试场景
懒汉式(同步方法)低并发、对性能无要求
双重校验锁高并发生产环境
静态内部类大多数生产环境(推荐)
枚举单例高稳定性核心业务(推荐)

五、使用建议

  1. 优先选择静态内部类或枚举单例:静态内部类兼顾懒加载与性能,枚举单例兼顾稳定性与简洁性;
  2. 避免使用懒汉式(无锁/同步方法) :无锁线程不安全,同步方法性能差;
  3. 饿汉式仅用于小实例:避免大实例预加载造成内存浪费;
  4. 核心业务用枚举单例:防反射、防序列化,稳定性最高;
  5. 避免滥用单例:仅用于需唯一实例的场景,否则会导致代码耦合度升高。