Skip to content

单例模式 ​

单例模式确保一个类只有一个实例,并提供全局访问点。这是最简单的设计模式之一,也是最常用的模式之一。

📋 模式概述 ​

定义: 保证一个类仅有一个实例,并提供一个访问它的全局访问点。

适用场景:

  • 需要频繁创建和销毁的对象
  • 创建对象时耗时过多或耗资源过多
  • 工具类对象
  • 数据库连接池
  • 线程池
  • 配置管理器
  • 日志记录器

🎯 一、项目案例:数据库连接池管理 ​

1.1 问题场景 ​

在电商系统中,我们需要管理数据库连接池。如果每次创建新的连接池,会导致:

  • 资源浪费
  • 连接数过多
  • 管理复杂

1.2 单例模式解决方案 ​

项目背景: 电商系统数据库连接池管理器

java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * 数据库连接池管理器 - 单例模式实现
 * 项目案例:电商系统数据库连接管理
 */
public class DatabaseConnectionPool {
    
    // 私有静态实例
    private static volatile DatabaseConnectionPool instance;
    
    // 连接池配置
    private static final String DB_URL = "jdbc:mysql://localhost:3306/ecommerce";
    private static final String DB_USER = "root";
    private static final String DB_PASSWORD = "password";
    private static final int POOL_SIZE = 10;
    
    // 连接池队列
    private BlockingQueue<Connection> connectionPool;
    private int currentConnections = 0;
    
    // 私有构造函数,防止外部实例化
    private DatabaseConnectionPool() {
        initializePool();
    }
    
    /**
     * 双重检查锁定实现线程安全的单例
     */
    public static DatabaseConnectionPool getInstance() {
        if (instance == null) {
            synchronized (DatabaseConnectionPool.class) {
                if (instance == null) {
                    instance = new DatabaseConnectionPool();
                }
            }
        }
        return instance;
    }
    
    /**
     * 初始化连接池
     */
    private void initializePool() {
        connectionPool = new LinkedBlockingQueue<>(POOL_SIZE);
        try {
            // 预创建连接
            for (int i = 0; i < POOL_SIZE; i++) {
                Connection conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);
                connectionPool.offer(conn);
                currentConnections++;
            }
            System.out.println("数据库连接池初始化完成,连接数: " + currentConnections);
        } catch (SQLException e) {
            throw new RuntimeException("初始化数据库连接池失败", e);
        }
    }
    
    /**
     * 获取连接
     */
    public Connection getConnection() throws InterruptedException {
        Connection conn = connectionPool.take();
        if (conn == null || conn.isClosed()) {
            // 连接已关闭,创建新连接
            conn = createNewConnection();
        }
        return conn;
    }
    
    /**
     * 归还连接
     */
    public void releaseConnection(Connection conn) {
        if (conn != null && !connectionPool.offer(conn)) {
            // 连接池已满,关闭连接
            try {
                conn.close();
                currentConnections--;
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    
    /**
     * 创建新连接
     */
    private Connection createNewConnection() {
        try {
            return DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);
        } catch (SQLException e) {
            throw new RuntimeException("创建数据库连接失败", e);
        }
    }
    
    /**
     * 关闭连接池
     */
    public void shutdown() {
        while (!connectionPool.isEmpty()) {
            try {
                Connection conn = connectionPool.poll();
                if (conn != null && !conn.isClosed()) {
                    conn.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        System.out.println("数据库连接池已关闭");
    }
}

1.3 使用示例 ​

java
/**
 * 订单服务 - 使用单例连接池
 */
public class OrderService {
    
    private DatabaseConnectionPool connectionPool;
    
    public OrderService() {
        // 获取单例连接池实例
        this.connectionPool = DatabaseConnectionPool.getInstance();
    }
    
    /**
     * 创建订单
     */
    public void createOrder(Order order) {
        Connection conn = null;
        try {
            // 从连接池获取连接
            conn = connectionPool.getConnection();
            
            // 执行数据库操作
            String sql = "INSERT INTO orders (order_id, user_id, amount, status) VALUES (?, ?, ?, ?)";
            PreparedStatement ps = conn.prepareStatement(sql);
            ps.setString(1, order.getOrderId());
            ps.setLong(2, order.getUserId());
            ps.setBigDecimal(3, order.getAmount());
            ps.setString(4, order.getStatus());
            ps.executeUpdate();
            
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 归还连接
            if (conn != null) {
                connectionPool.releaseConnection(conn);
            }
        }
    }
}

好处:

  1. 资源节省: 整个系统只有一个连接池实例,避免重复创建
  2. 统一管理: 所有数据库操作使用同一个连接池,便于监控和管理
  3. 线程安全: 使用双重检查锁定确保多线程环境下的安全性

🎯 二、项目案例:配置管理器 ​

2.1 问题场景 ​

在微服务系统中,我们需要统一管理配置信息。如果每个服务都创建自己的配置管理器,会导致:

  • 配置分散
  • 难以统一修改
  • 内存浪费

2.2 单例模式解决方案 ​

java
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

/**
 * 配置管理器 - 单例模式实现
 * 项目案例:微服务系统配置管理
 */
public class ConfigManager {
    
    private static ConfigManager instance;
    private Properties properties;
    
    // 私有构造函数
    private ConfigManager() {
        loadConfig();
    }
    
    /**
     * 懒汉式单例 - 线程安全版本
     */
    public static synchronized ConfigManager getInstance() {
        if (instance == null) {
            instance = new ConfigManager();
        }
        return instance;
    }
    
    /**
     * 加载配置文件
     */
    private void loadConfig() {
        properties = new Properties();
        try (InputStream is = getClass().getClassLoader()
                .getResourceAsStream("application.properties")) {
            if (is != null) {
                properties.load(is);
                System.out.println("配置文件加载成功");
            }
        } catch (IOException e) {
            throw new RuntimeException("加载配置文件失败", e);
        }
    }
    
    /**
     * 获取配置值
     */
    public String getProperty(String key) {
        return properties.getProperty(key);
    }
    
    /**
     * 获取配置值(带默认值)
     */
    public String getProperty(String key, String defaultValue) {
        return properties.getProperty(key, defaultValue);
    }
    
    /**
     * 获取整数配置
     */
    public int getIntProperty(String key, int defaultValue) {
        String value = properties.getProperty(key);
        if (value != null) {
            try {
                return Integer.parseInt(value);
            } catch (NumberFormatException e) {
                return defaultValue;
            }
        }
        return defaultValue;
    }
    
    /**
     * 获取布尔配置
     */
    public boolean getBooleanProperty(String key, boolean defaultValue) {
        String value = properties.getProperty(key);
        if (value != null) {
            return Boolean.parseBoolean(value);
        }
        return defaultValue;
    }
}

2.3 使用示例 ​

java
/**
 * 用户服务 - 使用单例配置管理器
 */
public class UserService {
    
    private ConfigManager configManager;
    
    public UserService() {
        // 获取单例配置管理器
        this.configManager = ConfigManager.getInstance();
    }
    
    /**
     * 发送邮件验证码
     */
    public void sendVerificationEmail(String email) {
        // 从配置管理器获取邮件服务器配置
        String smtpHost = configManager.getProperty("email.smtp.host");
        int smtpPort = configManager.getIntProperty("email.smtp.port", 587);
        String username = configManager.getProperty("email.username");
        String password = configManager.getProperty("email.password");
        
        // 发送邮件逻辑
        EmailSender sender = new EmailSender(smtpHost, smtpPort, username, password);
        sender.send(email, "验证码", generateCode());
    }
}

好处:

  1. 统一配置: 所有服务使用同一个配置管理器,配置集中管理
  2. 内存节省: 只加载一次配置文件,避免重复加载
  3. 易于维护: 配置修改只需在一个地方进行

🎯 三、单例模式的多种实现方式 ​

3.1 饿汉式(线程安全) ​

java
/**
 * 饿汉式单例 - 线程安全
 * 优点:线程安全,实现简单
 * 缺点:类加载时就创建实例,可能浪费内存
 */
public class EagerSingleton {
    
    // 类加载时创建实例
    private static final EagerSingleton instance = new EagerSingleton();
    
    private EagerSingleton() {}
    
    public static EagerSingleton getInstance() {
        return instance;
    }
}

3.2 懒汉式(线程不安全) ​

java
/**
 * 懒汉式单例 - 线程不安全
 * 优点:延迟加载,节省内存
 * 缺点:多线程环境下不安全
 */
public class LazySingleton {
    
    private static LazySingleton instance;
    
    private LazySingleton() {}
    
    public static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();  // 多线程可能创建多个实例
        }
        return instance;
    }
}

3.3 懒汉式(线程安全) ​

java
/**
 * 懒汉式单例 - synchronized版本(线程安全)
 * 优点:线程安全
 * 缺点:每次获取实例都要加锁,性能较差
 */
public class SynchronizedSingleton {
    
    private static SynchronizedSingleton instance;
    
    private SynchronizedSingleton() {}
    
    public static synchronized SynchronizedSingleton getInstance() {
        if (instance == null) {
            instance = new SynchronizedSingleton();
        }
        return instance;
    }
}

3.4 双重检查锁定(推荐) ​

java
/**
 * 双重检查锁定单例 - 线程安全且性能好
 * 优点:线程安全,延迟加载,性能好
 * 缺点:实现相对复杂
 */
public class DoubleCheckSingleton {
    
    // 使用volatile确保可见性
    private static volatile DoubleCheckSingleton instance;
    
    private DoubleCheckSingleton() {}
    
    public static DoubleCheckSingleton getInstance() {
        if (instance == null) {
            synchronized (DoubleCheckSingleton.class) {
                if (instance == null) {
                    instance = new DoubleCheckSingleton();
                }
            }
        }
        return instance;
    }
}

3.5 静态内部类(推荐) ​

java
/**
 * 静态内部类单例 - 线程安全且延迟加载
 * 优点:线程安全,延迟加载,实现简单,推荐使用
 */
public class InnerClassSingleton {
    
    private InnerClassSingleton() {}
    
    /**
     * 静态内部类,只有在被调用时才会加载
     */
    private static class SingletonHolder {
        private static final InnerClassSingleton instance = new InnerClassSingleton();
    }
    
    public static InnerClassSingleton getInstance() {
        return SingletonHolder.instance;
    }
}

3.6 枚举单例(推荐) ​

java
/**
 * 枚举单例 - 线程安全,防止反序列化和反射攻击
 * 优点:线程安全,实现简单,防止反序列化和反射攻击
 * 缺点:不能延迟加载
 */
public enum EnumSingleton {
    INSTANCE;
    
    // 可以添加方法
    public void doSomething() {
        System.out.println("枚举单例执行操作");
    }
}

// 使用方式
EnumSingleton.INSTANCE.doSomething();

🎯 四、Spring中的单例 ​

4.1 Spring单例模式 ​

Spring容器默认就是单例模式:

java
@Service
public class UserService {
    // Spring容器中默认就是单例
}

@Controller
public class UserController {
    
    @Autowired
    private UserService userService;  // 注入的是同一个实例
}

4.2 自定义单例Bean ​

java
@Component
public class CacheManager {
    
    private Map<String, Object> cache = new ConcurrentHashMap<>();
    
    public void put(String key, Object value) {
        cache.put(key, value);
    }
    
    public Object get(String key) {
        return cache.get(key);
    }
}

📖 最佳实践 ​

1. 选择合适的实现方式 ​

  • 需要延迟加载: 使用静态内部类或双重检查锁定
  • 不需要延迟加载: 使用饿汉式或枚举
  • 需要防止反射和反序列化: 使用枚举

2. 注意线程安全 ​

  • 多线程环境必须使用线程安全的实现
  • 推荐使用静态内部类或枚举方式

3. 避免过度使用 ​

  • 不是所有需要唯一实例的场景都适合单例
  • 单例模式会增加代码的耦合度

✅ 学习检查清单 ​

  • [ ] 已理解单例模式的定义和适用场景
  • [ ] 已掌握各种单例实现方式
  • [ ] 已理解线程安全的重要性
  • [ ] 已在项目中应用单例模式
  • [ ] 已了解单例模式的优缺点

上一章节:设计模式概览
下一章节:工厂模式
返回目录:设计模式