DukeDuke
主页
关于我们
主页
关于我们
  • Java

    • Java基础

      • 内存与磁盘
      • 进制转换
      • 数据存储
      • Java基本数据类型
      • HashMap
      • Java四大引用
    • JVM

      • 认识JVM
      • JVM类加载器
      • 运行时数据区
      • 执行引擎
      • 本地方法接口
      • 本地方法库
      • JVM垃圾回收
      • JVM性能监控
      • JVM调优
    • 设计模式
      • 单例模式
      • 工厂模式
      • 策略模式
      • 适配器模式
      • 建造者模式
      • 原型模式
      • 装饰器模式
      • 代理模式
      • 外观模式
      • 享元模式
      • 组合模式
      • 桥接模式
    • Java多线程

      • Java 线程基础详解
      • Java 线程池详解
      • Java ThreadLocal 详解
      • Java volatile 详解
      • Java 线程间通信详解
      • Java 线程安全详解
      • Java 线程调度详解
      • Java 线程优先级详解

      • Java 线程中断详解
      • Java 线程死锁详解
    • Java反射
    • Java 面试题

      • Java 基础概念面试题
      • Java 面向对象编程面试题
      • Java 集合框架面试题
      • Java 多线程与并发面试题
      • JVM 与内存管理面试题
      • Java I/O 与 NIO 面试题
      • Java 异常处理面试题
      • Java 反射与注解面试题
      • Java Spring 框架面试题
      • Java 数据库与 JDBC 面试题
      • Java 性能优化面试题
      • Java 实际项目经验面试题
      • Java 高级特性面试题
      • Java 面试准备建议

单例模式

什么是单例模式?

单例模式是一种设计模式,确保一个类只有一个实例,并提供一个全局访问点。简单来说,就是保证某个类在整个程序中只被创建一次,就像现实生活中的"唯一"概念一样。

为什么需要单例模式?

想象一下这些场景:

  • 数据库连接:我们只需要一个数据库连接池,多个连接会浪费资源
  • 配置管理器:程序的配置信息只需要加载一次
  • 日志记录器:整个程序使用同一个日志记录器
  • 缓存管理器:统一管理内存缓存

单例模式的特点

  1. 私有构造函数:防止外部直接创建实例
  2. 私有静态实例:保存唯一的实例
  3. 公共静态访问方法:提供全局访问点

示例 1:饿汉式单例(线程安全)

public class DatabaseConnection {
    // 私有静态实例,在类加载时就创建
    private static final DatabaseConnection instance = new DatabaseConnection();

    // 私有构造函数,防止外部创建实例
    private DatabaseConnection() {
        System.out.println("数据库连接已创建");
    }

    // 公共静态方法,提供全局访问点
    public static DatabaseConnection getInstance() {
        return instance;
    }

    public void connect() {
        System.out.println("连接到数据库...");
    }

    public void disconnect() {
        System.out.println("断开数据库连接...");
    }
}

// 使用示例
public class Main {
    public static void main(String[] args) {
        // 获取数据库连接实例
        DatabaseConnection db1 = DatabaseConnection.getInstance();
        DatabaseConnection db2 = DatabaseConnection.getInstance();

        // 验证是否是同一个实例
        System.out.println("db1 和 db2 是同一个实例吗?" + (db1 == db2));

        db1.connect();
        db2.disconnect();
    }
}

输出结果:

数据库连接已创建
db1 和 db2 是同一个实例吗?true
连接到数据库...
断开数据库连接...

示例 2:懒汉式单例(线程安全)

public class Logger {
    // 私有静态实例,初始为null
    private static volatile Logger instance;

    // 私有构造函数
    private Logger() {
        System.out.println("日志记录器已初始化");
    }

    // 双重检查锁定,确保线程安全
    public static Logger getInstance() {
        if (instance == null) {
            synchronized (Logger.class) {
                if (instance == null) {
                    instance = new Logger();
                }
            }
        }
        return instance;
    }

    public void log(String message) {
        System.out.println("[LOG] " + message);
    }

    public void error(String message) {
        System.out.println("[ERROR] " + message);
    }
}

// 使用示例
public class Application {
    public static void main(String[] args) {
        // 第一次调用时才创建实例
        Logger logger1 = Logger.getInstance();
        Logger logger2 = Logger.getInstance();

        System.out.println("logger1 和 logger2 是同一个实例吗?" + (logger1 == logger2));

        logger1.log("应用启动");
        logger2.error("发生错误");
    }
}

输出结果:

日志记录器已初始化
logger1 和 logger2 是同一个实例吗?true
[LOG] 应用启动
[ERROR] 发生错误

示例 3:枚举单例(推荐方式)

public enum ConfigManager {
    INSTANCE;

    private String databaseUrl;
    private String username;
    private String password;

    ConfigManager() {
        System.out.println("配置管理器已加载");
        // 从配置文件加载配置
        loadConfig();
    }

    private void loadConfig() {
        this.databaseUrl = "jdbc:mysql://localhost:3306/mydb";
        this.username = "admin";
        this.password = "123456";
    }

    public String getDatabaseUrl() {
        return databaseUrl;
    }

    public String getUsername() {
        return username;
    }

    public String getPassword() {
        return password;
    }

    public void updateConfig(String databaseUrl, String username, String password) {
        this.databaseUrl = databaseUrl;
        this.username = username;
        this.password = password;
        System.out.println("配置已更新");
    }
}

// 使用示例
public class ConfigTest {
    public static void main(String[] args) {
        ConfigManager config1 = ConfigManager.INSTANCE;
        ConfigManager config2 = ConfigManager.INSTANCE;

        System.out.println("config1 和 config2 是同一个实例吗?" + (config1 == config2));

        System.out.println("数据库URL: " + config1.getDatabaseUrl());
        System.out.println("用户名: " + config1.getUsername());

        // 更新配置
        config2.updateConfig("jdbc:mysql://localhost:3306/newdb", "newuser", "newpass");

        // 验证配置已更新
        System.out.println("更新后的数据库URL: " + config1.getDatabaseUrl());
    }
}

输出结果:

配置管理器已加载
config1 和 config2 是同一个实例吗?true
数据库URL: jdbc:mysql://localhost:3306/mydb
用户名: admin
配置已更新
更新后的数据库URL: jdbc:mysql://localhost:3306/newdb

单例模式的优缺点

优点:

  • 节省资源:避免重复创建对象,节省内存和 CPU 资源
  • 全局访问:提供全局访问点,方便管理
  • 保证唯一性:确保某个类只有一个实例

缺点:

  • 违反单一职责原则:单例类既要管理自己的实例,又要提供业务功能
  • 难以测试:全局状态使得单元测试变得困难
  • 可能产生隐藏依赖:其他类可能过度依赖单例

使用场景

  1. 配置管理:应用程序的配置信息
  2. 数据库连接池:管理数据库连接
  3. 日志记录器:统一的日志记录
  4. 缓存管理器:内存缓存管理
  5. 线程池:管理线程资源

总结

单例模式是 Java 中最常用的设计模式之一,它确保一个类只有一个实例,并提供全局访问点。通过不同的实现方式(饿汉式、懒汉式、枚举),我们可以根据具体需求选择最合适的方案。在实际开发中,要谨慎使用单例模式,避免过度设计。

最近更新:: 2025/12/29 11:07
Contributors: Duke
Next
工厂模式