设计模式--单例模式

基本概述

单例模式(Singleton Pattern)是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取该实例。

这种模式常用于需要频繁创建和销毁的对象、或者需要协调系统中多个部分的共享资源的等场景

核心特点

1、唯一实例:类只能有一个实例

2、自行创建:类必须自行创建这个实例

3、向整个系统提供这个实例

实现方式

1、饿汉式(线程安全)

饿汉式单例模式是单例模式中最简单也是最直观的一种实现方式。他的核心特点是:在类加载时就立即创建实例,因此成为"饿汉"式(迫不及待的创建实例)

特点
  • 类还在的时候就创建实例,而不是在首次使用的时候
  • 线程安全:由于在类加载的时候就创建了实例,所以天然线程安全
  • 实现简单:逻辑代码清晰,易于理解和维护
  • 资源预加载:实例在类加载的时候就占用内存,可能导致资源浪费
基本实现
复制代码
package gy_basic_project.singleton.demo;

/**
 * 饿汉式单例模式 - 基本实现
 * 在类加载时就创建实例,线程安全
 */
public class EagerSingleton {
    // 在类加载时就创建实例
    private static final EagerSingleton INSTANCE = new EagerSingleton();
    
    // 私有构造函数,防止外部创建实例
    private EagerSingleton() {
        System.out.println("EagerSingleton实例被创建");
    }
    
    public static EagerSingleton getInstance() {
        return INSTANCE;
    }
    
    public void showMessage(String message) {
        System.out.println("EagerSingleton: " + message);
    }
}
饿汉式的变体:静态代码块实现
复制代码
package gy_basic_project.singleton.demo;

/**
 * 饿汉式单例模式 - 静态代码块实现
 * 与基本实现类似,但在静态代码块中创建实例,便于复杂初始化
 */
public class EagerSingletonWithStaticBlock {
    private static final EagerSingletonWithStaticBlock INSTANCE;
    
    // 静态代码块中初始化,可以进行复杂的初始化操作
    static {
        System.out.println("开始初始化EagerSingletonWithStaticBlock...");
        INSTANCE = new EagerSingletonWithStaticBlock();
        System.out.println("EagerSingletonWithStaticBlock初始化完成");
    }
    
    private EagerSingletonWithStaticBlock() {
        System.out.println("EagerSingletonWithStaticBlock实例被创建");
    }
    
    public static EagerSingletonWithStaticBlock getInstance() {
        return INSTANCE;
    }
    
    public void showMessage(String message) {
        System.out.println("EagerSingletonWithStaticBlock: " + message);
    }
    
    public void performInitialization() {
        System.out.println("执行一些初始化操作...");
    }
}
饿汉式在实际应用中的例子

让我们看一个实际的应用场景,比如数据库连接配置管理器:

复制代码
package gy_basic_project.singleton.demo;

import java.util.Properties;

/**
 * 数据库配置管理器 - 饿汉式实现
 * 应用启动时加载配置,供整个应用使用
 */
public class DatabaseConfigManager {
    // 饿汉式:在类加载时就初始化配置
    private static final DatabaseConfigManager INSTANCE = new DatabaseConfigManager();
    
    // 存储数据库配置
    private Properties dbProperties;
    
    private DatabaseConfigManager() {
        System.out.println("初始化数据库配置管理器...");
        loadConfiguration();
    }
    
    private void loadConfiguration() {
        dbProperties = new Properties();
        // 模拟加载默认配置
        dbProperties.setProperty("db.url", "jdbc:mysql://localhost:3306/myapp");
        dbProperties.setProperty("db.username", "root");
        dbProperties.setProperty("db.password", "password");
        dbProperties.setProperty("db.driver", "com.mysql.cj.jdbc.Driver");
        dbProperties.setProperty("db.poolSize", "10");
        
        System.out.println("数据库配置加载完成");
    }
    
    public static DatabaseConfigManager getInstance() {
        return INSTANCE;
    }
    
    public String getProperty(String key) {
        return dbProperties.getProperty(key);
    }
    
    public void setProperty(String key, String value) {
        dbProperties.setProperty(key, value);
    }
    
    public Properties getAllProperties() {
        return (Properties) dbProperties.clone();
    }
}
饿汉式使用示例
复制代码
package gy_basic_project.singleton.demo;

/**
 * 饿汉式单例模式演示
 */
public class EagerSingletonDemo {
    public static void main(String[] args) {
        System.out.println("=== 饿汉式单例模式演示 ===\n");
        
        // 1. 基本饿汉式单例演示
        System.out.println("1. 基本饿汉式单例演示:");
        System.out.println("首次获取实例:");
        EagerSingleton eager1 = EagerSingleton.getInstance();
        eager1.showMessage("这是第一个实例调用");
        
        System.out.println("再次获取实例:");
        EagerSingleton eager2 = EagerSingleton.getInstance();
        eager2.showMessage("这是第二个实例调用");
        
        System.out.println("eager1 == eager2: " + (eager1 == eager2)); // true
        System.out.println();
        
        // 2. 静态代码块饿汉式演示
        System.out.println("2. 静态代码块饿汉式单例演示:");
        EagerSingletonWithStaticBlock staticBlock1 = EagerSingletonWithStaticBlock.getInstance();
        staticBlock1.performInitialization();
        staticBlock1.showMessage("这是静态代码块饿汉式实例");
        
        EagerSingletonWithStaticBlock staticBlock2 = EagerSingletonWithStaticBlock.getInstance();
        System.out.println("staticBlock1 == staticBlock2: " + (staticBlock1 == staticBlock2)); // true
        System.out.println();
        
        // 3. 数据库配置管理器演示
        System.out.println("3. 数据库配置管理器演示:");
        DatabaseConfigManager config1 = DatabaseConfigManager.getInstance();
        System.out.println("数据库URL: " + config1.getProperty("db.url"));
        System.out.println("用户名: " + config1.getProperty("db.username"));
        
        // 修改配置
        config1.setProperty("db.poolSize", "20");
        
        // 获取同一实例,验证配置已更新
        DatabaseConfigManager config2 = DatabaseConfigManager.getInstance();
        System.out.println("更新后的连接池大小: " + config2.getProperty("db.poolSize"));
        System.out.println("config1 == config2: " + (config1 == config2)); // true
        System.out.println();
        
        // 4. 多线程安全性演示
        System.out.println("4. 多线程安全性演示:");
        Thread[] threads = new Thread[5];
        EagerSingleton[] instances = new EagerSingleton[5];
        
        for (int i = 0; i < 5; i++) {
            final int index = i;
            threads[i] = new Thread(() -> {
                instances[index] = EagerSingleton.getInstance();
                System.out.println("线程 " + Thread.currentThread().getName() + 
                                 " 获取实例: " + instances[index].hashCode());
            });
        }
        
        for (Thread thread : threads) {
            thread.start();
        }
        
        for (Thread thread : threads) {
            try {
                thread.join();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        
        // 验证实例相同
        boolean allSame = true;
        int baseHash = instances[0].hashCode();
        for (int i = 1; i < instances.length; i++) {
            if (instances[i].hashCode() != baseHash) {
                allSame = false;
                break;
            }
        }
        System.out.println("所有线程获取的实例相同: " + allSame);
        
        System.out.println("\n=== 饿汉式单例演示完成 ===");
    }
}
饿汉式的优缺点

优点

  • 线程安全:由于在类记载时就创建实例,不需要额外的同步措施
  • 实现简单:代码逻辑清晰,易于理解和维护
  • 性能好:获取实例的时候无需额外操作,性能优秀
  • 类加载安全:利用类加载机制保证实例唯一性

缺点:

  • 资源浪费:即使不使用实例也会占用内存
  • 无法延迟加载:不能按需创建实例
  • 初始化时机不可控:在类加载时就创建,可能在应用启动早期就占用资源
适用场景
  • 实例占用资源不多的
  • 初始化较快
  • 程序启动时必然会使用该实例
  • 多线程安全较高的场景

2、懒汉式(线程不安全)

懒汉式(非线程安全)是单例模式的一种实现方式,其特点是只在需要时才创建实例,体现了"懒加载"的思想

特点
  • 延迟加载:只有在首次调用getInstance()方法的时候才创建实例
  • 节省资源:如果实例从未被使用,就不会被创建,节省内存
  • 非线程安全:在多线程环境下可能出现多个实例的问题
  • 实现简单:代码逻辑清晰,易于理解
基本实现
复制代码
package gy_basic_project.singleton.demo;

/**
 * 懒汉式单例模式(非线程安全)
 * 在第一次调用getInstance()时才创建实例
 */
public class LazySingleton {
    private static LazySingleton instance;
    
    private LazySingleton() {
        System.out.println("LazySingleton实例被创建");
    }
    
    public static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
    
    public void showMessage(String message) {
        System.out.println("LazySingleton: " + message);
    }
}
为什么会线程不安全?

再多线程环境下,懒汉式会出现竞态条件(Race Condition)

1、线程A检查instance是否为null,结果为true;

2、此时线程B也检查instance是否为null,结果也是true

3、线程A继续执行,创建实例

4、线程B也继续执行,创建另一个实例

5、最终导致创建了多个实例

复制代码
package gy_basic_project.singleton.demo;

/**
 * 懒汉式单例模式演示
 */
public class LazySingletonDemo {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("=== 懒汉式单例模式演示 ===\n");
        
        // 1. 基本使用演示
        System.out.println("1. 基本使用演示:");
        System.out.println("首次获取实例:");
        LazySingleton lazy1 = LazySingleton.getInstance();
        lazy1.showMessage("这是第一个实例");
        
        System.out.println("再次获取实例:");
        LazySingleton lazy2 = LazySingleton.getInstance();
        lazy2.showMessage("这是第二个实例");
        
        System.out.println("lazy1 == lazy2: " + (lazy1 == lazy2)); // true(在单线程下)
        System.out.println();
        
        // 2. 延迟加载演示 - 实例在首次调用时才创建
        System.out.println("2. 延迟加载演示:");
        System.out.println("声明前,实例尚未创建");
        
        // 只有在调用getInstance()时才创建实例
        LazySingleton lazy3 = LazySingleton.getInstance();
        System.out.println("实例创建后: " + (lazy1 == lazy3)); // true
        System.out.println();
        
        // 3. 线程安全问题演示
        System.out.println("3. 线程安全问题演示:");
        demonstrateThreadSafetyIssue();
        
        System.out.println("\n=== 懒汉式单例演示完成 ===");
    }
    
    /**
     * 演示懒汉式在多线程环境下的问题
     */
    private static void demonstrateThreadSafetyIssue() throws InterruptedException {
        // 重置实例(仅用于演示)
        resetLazySingleton();
        
        // 创建多个线程同时获取实例
        Thread[] threads = new Thread[10];
        LazySingleton[] instances = new LazySingleton[10];
        
        System.out.println("创建10个线程同时获取实例...");
        
        for (int i = 0; i < 10; i++) {
            final int index = i;
            threads[i] = new Thread(() -> {
                // 添加短暂延时,增加竞争条件发生的可能性
                try {
                    Thread.sleep((long) (Math.random() * 10));
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                
                instances[index] = LazySingleton.getInstance();
                System.out.println(Thread.currentThread().getName() + 
                                 " 获取实例,哈希码: " + instances[index].hashCode());
            }, "Thread-" + i);
        }
        
        // 启动所有线程
        for (Thread thread : threads) {
            thread.start();
        }
        
        // 等待所有线程完成
        for (Thread thread : threads) {
            thread.join();
        }
        
        // 检查是否所有线程获得了相同的实例
        System.out.println("\n检查实例唯一性:");
        boolean allSame = true;
        LazySingleton firstInstance = instances[0];
        for (int i = 1; i < instances.length; i++) {
            if (instances[i] != firstInstance) {
                allSame = false;
                System.out.println("发现不同实例!线程" + i + "获得不同实例");
            }
        }
        
        System.out.println("所有线程获得相同实例: " + allSame);
        
        if (!allSame) {
            System.out.println("检测到线程安全问题:创建了多个实例!");
        } else {
            System.out.println("未观察到线程安全问题(可能是由于执行速度太快,没有发生竞争)");
        }
    }
    
    /**
     * 重置LazySingleton实例(仅用于演示目的)
     */
    @SuppressWarnings("unchecked")
    private static void resetLazySingleton() {
        try {
            java.lang.reflect.Field instanceField = LazySingleton.class.getDeclaredField("instance");
            instanceField.setAccessible(true);
            instanceField.set(null, null);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

改进版本:带延迟的懒汉式(更容易重现问题)

复制代码
package gy_basic_project.singleton.demo;

/**
 * 懒汉式单例模式(非线程安全)- 用于演示问题的版本
 * 在创建实例前添加延迟,更容易观察到线程安全问题
 */
public class ProblematicLazySingleton {
    private static ProblematicLazySingleton instance;
    
    private ProblematicLazySingleton() {
        // 模拟构造函数执行时间
        try {
            Thread.sleep(1); // 添加延迟,更容易产生竞态条件
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        System.out.println("ProblematicLazySingleton实例被创建,线程: " + 
                          Thread.currentThread().getName());
    }
    
    public static ProblematicLazySingleton getInstance() {
        if (instance == null) {
            // 在这里添加延迟,让竞态条件更容易发生
            try {
                Thread.sleep(1); // 增加竞态条件发生的概率
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            instance = new ProblematicLazySingleton();
        }
        return instance;
    }
    
    public void showMessage(String message) {
        System.out.println("ProblematicLazySingleton: " + message + 
                          " (实例哈希码: " + this.hashCode() + ")");
    }
}
懒汉式的优缺点

优点

  • 延迟加载:只在需要时才创建实例,节省内存
  • 实现简单:代码逻辑清晰,易于理解
  • 按需创建:如果实例从未使用,则不会创建

缺点

  • 线程不安全:在多线程环境下可能创建多个实例
  • 不适合高并发:在高并发场景下会导致问题

多线程高并发环境下,可以是用同步锁或者双重检查锁定

3、懒汉式(线程安全)

在懒汉式(非线程安全)的基础上实现,默认的实现方法是同步方法的方式,后续通过变体分别演变为"双重检查锁定""静态内部类"

当前我们先讲解同步方法版本懒汉式

懒汉式--同步方法方式(线程安全但性能较低)

只是在获取实例的方法上添加同步锁用来控制多线程环境下的安全

只适用于对于性能要求不高的程序中使用

复制代码
package gy_basic_project.singleton.demo;

/**
 * 懒汉式单例模式 - 线程安全版本(同步方法)
 * 通过synchronized关键字保证线程安全,但性能较低
 */
public class ThreadSafeLazySingleton {
    private static ThreadSafeLazySingleton instance;
    
    private ThreadSafeLazySingleton() {
        System.out.println("ThreadSafeLazySingleton实例被创建,线程: " + 
                          Thread.currentThread().getName());
    }
    
    /**
     * 使用synchronized关键字确保线程安全
     * 但每次调用都会进行同步,影响性能
     */
    public static synchronized ThreadSafeLazySingleton getInstance() {
        if (instance == null) {
            instance = new ThreadSafeLazySingleton();
        }
        return instance;
    }
    
    public void showMessage(String message) {
        System.out.println("ThreadSafeLazySingleton: " + message);
    }
}

4、双重检查锁定(推荐)

双重检查锁定(Duble-Checked Loking)是一种优化的单例模式实现方式,他在保持懒加载特性的同时确保线程的安全,同时避免了每次访问需要同步的性能开销

基本实现
复制代码
package gy_basic_project.singleton.demo;

/**
 * 双重检查锁定单例模式
 * 结合了懒加载和线程安全的特性,同时避免了每次访问都要同步
 */
public class DoubleCheckedLockingSingleton {
    /**
     * 使用volatile关键字的重要性:
     * 1. 确保变量的可见性:一个线程修改后,其他线程能立即看到变化
     * 2. 防止指令重排序:确保对象完全初始化后再赋值给instance引用
     */
    private static volatile DoubleCheckedLockingSingleton instance;
    
    private DoubleCheckedLockingSingleton() {
        System.out.println("DoubleCheckedLockingSingleton实例被创建,线程: " + 
                          Thread.currentThread().getName());
    }
    
    public static DoubleCheckedLockingSingleton getInstance() {
        // 第一次检查:无锁检查,提高性能
        if (instance == null) {
            // 进入同步块,确保只创建一个实例
            synchronized (DoubleCheckedLockingSingleton.class) {
                // 第二次检查:确保只创建一个实例
                if (instance == null) {
                    instance = new DoubleCheckedLockingSingleton();
                }
            }
        }
        return instance;
    }
    
    public void showMessage(String message) {
        System.out.println("DoubleCheckedLockingSingleton: " + message);
    }
}
双重检查锁定的工作流程

1、第一次检查(无锁)

  • 如果实例已经存在,直接返回避免不必要的同步开销

  • 提高了获取实例的性能

    if (instance == null) {
    // 如果instance不为null,直接返回,无需进入同步块
    }

2、同步块

  • 当时李不存在的时候进入同步块

  • 防止多个线程同时创建实例

    synchronized (DoubleCheckedLockingSingleton.class) {
    // 确保只创建一个实例
    }

3、第二次检查(同步块内)

  • 在同步块内再次检查,确保只创建一个实例

  • 防止在等待锁的过程中,其他线程已经创建了实例

    if (instance == null) {
    instance = new DoubleCheckedLockingSingleton();
    }

为什么需要volatile关键字?

无volatile时可能出现的问题

  • 线程A创建实例后,由于CPU缓存机制线程B可能看不到线程A的修改,继续创建新实例
  • 指令重排问题
    • 在JVM中,new操作可能被分解为以下步骤
      • 1、分配内存
      • 初始化对象
      • 将instance指向被分配的内存地址
    • 如果发生重排序变成了1->3->2:
      • instance指向了内存地址(不为null)
      • 但对象尚未初始化完成
      • 其他线程可能获取到未完全化的对象

5、静态内部类(推荐)

特点
  • 延迟加载:实例在第一次调用getInstance()方法的时候才会被调用
  • 线程安全:利用JVM类加载机制保证线程安全,无需额外同步
  • 高性能:避免同步带来的性能开销
基本实现
复制代码
public class Singleton {
    // 私有构造函数,防止外部实例化
    private Singleton() {}

    // 静态内部类,持有单例实例
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    // 提供全局访问点
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}
注意事项
  • 私有构造函数:必须为私有,防止外部通过new创建实例
  • 序列化问题:若类实现了Serializable接口,需要重写readResolve()方法防止反序列化破坏单例
  • 反射攻击:可通过在构造函数中添加逻辑判断防御反射创建多个实例
优缺点

优点:

  • 线程安全且无性能损耗
  • 实现简单逻辑清晰
  • 支持延迟加载

缺点:

  • 不支持带参的构造函数
  • 对反射和序列化的防护需要额外处理
适用场景
  • 需要延迟加载的单例对象
  • 多线程环境下的高性能单例需求
  • 不涉及复杂初始化逻辑的场景

6、枚举实现(最安全)

特点
  • 天然线程安全:枚举的实例化由JVM保证线程安全
  • 防止反射攻击:枚举类型无法通过反射创建新实例
  • 防止序列化破坏:枚举类型默认实现序列化机制,反序列化不会创建新对象
  • 简洁高效:代码量少,语义清晰
基本实现
复制代码
public enum Singleton {
    INSTANCE;

    public void doSomething() {
        // 业务逻辑
    }
}
注意事项
  • 功能限制:枚举类型无法继承其他类(因为已隐式继承Enum),但可实现接口
  • 初始化参数:若需要传递参数,可在枚举构造函数中处理,但需要注意参数的不可变性
  • 序列化兼容:虽然枚举天然支持序列化,但仍需要确保业务逻辑与序列化行为一致
优缺点

优点:

  • 最安全的单例实现方式
  • 自动处理线程安全和序列化问题
  • 代码简介,易于维护

缺点:

  • 不支持延迟加载(枚举类型实例在类加载时即创建)
  • 功能受限,无法继承其他类
适用场景
  • 需要绝对安全的单例实现
  • 无延迟加载需求的场景
  • 对代码简洁性和可维护性要求较高的项目

单例模式的优缺点

优点

1、内存节省:只存在一个实例减少内存开销

2、性能提升:避免重复创建和销毁对象

3、全局访问:提供了统一的访问点

4、严格控制:严格控制实例的数量

缺点

1、扩展困难:单例类不易扩展

2、测试困难:难以进行单元测试

3、职责过重:吃蛋过多的职责,违背单一职责原则

4、潜在耦合:可能增加模块间的耦合度

应用场景

  • 数据库连接池
  • 日志记录器
  • 缓存管理器
  • 配置管理器
  • 线程池

在java中,静态内部类和双重检查锁定是比较常见的实现方式,他们既保证了线程安全,又避免同步带来的性能影响。而枚举实现则是最安全的实现方式,可以防止反射攻击和序列化破坏单例;

相关推荐
摘星编程2 小时前
在OpenHarmony上用React Native:Text文本可点击链接
javascript·react native·react.js
范纹杉想快点毕业2 小时前
状态机设计模式与嵌入式系统开发完整指南
java·开发语言·网络·数据库·mongodb·设计模式·架构
一位搞嵌入式的 genius2 小时前
从 URL 到渲染:JavaScript 性能优化全链路指南
开发语言·前端·javascript·性能优化
芭拉拉小魔仙2 小时前
Vue 3 组合式 API 详解:告别 Mixins,拥抱函数式编程
前端·javascript·vue.js
别叫我->学废了->lol在线等2 小时前
taiwindcss的一些用法
前端·javascript
卷卷的小趴菜学编程2 小时前
项目篇----仿tcmalloc的内存池设计(page cache)
c++·缓存·单例模式·tcmalloc·内存池·span cache
短剑重铸之日2 小时前
《设计模式》第十篇:三大类型之行为型模式
java·后端·设计模式·责任链模式·访问者模式·行为型模式
前端摸鱼匠3 小时前
Vue 3 的ref在响应式对象中:介绍ref在reactive对象中的自动解包
前端·javascript·vue.js·前端框架·ecmascript
Polaris_YJH3 小时前
使用Vue3+Vite+Pinia+elementUI搭建初级企业级项目
前端·javascript·elementui·vue