【设计模式】-单例模式

简介

单例模式是一种创建型设计模式,确保某个类仅有一个实例,并提供一个全局访问点来访问该实例。

在单例模式中,类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一对象的方式,允许直接访问而无需每次实例化该类的新对象。

主要场景

单例模式的主要应用场景包括:

  • 需要频繁实例化然后销毁的对象。
  • 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
  • 有状态的工具类对象。
  • 频繁访问数据库或文件的对象。

通过应用单例模式,我们可以控制实例的数量,节省系统资源,同时加快对象访问速度,尤其适合对象需要被公用的场合,例如多个模块使用同一个数据源连接对象等。

实现机制

实现单例模式的关键在于确保只有一个实例被创建,并提供一个全局访问点。这通常通过以下方式实现:

  • 将构造函数设为私有,以防止其他类通过new操作符创建该类的实例。
  • 在类内部创建一个静态的私有实例。
  • 提供一个静态的公有方法,用于返回该类的唯一实例。如果该实例尚未创建,则通过调用私有构造函数来创建它;如果实例已经存在,则直接返回该实例。

注意多线程

需要注意的是,在多线程环境下,需要确保单例模式的线程安全性,以避免出现多个实例的情况。

单例模式确保线程安全的关键在于确保在多线程环境下,类的唯一实例能够被正确地创建和访问,而不会出现多个实例或者创建过程中的竞争条件。以下是一些常见的线程安全的单例模式实现方式:

饿汉式(静态初始化):

这种方式是最简单的线程安全的实现方式。因为静态初始化器由JVM在加载类时执行,且JVM保证类的加载过程是线程安全的,所以在多线程环境下,这种方式创建的实例也是线程安全的。

java 复制代码
public class Singleton {
    // 在加载时就完成了初始化,所以类加载较慢,但获取对象的速度快
    private static Singleton instance = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return instance;
    }
}

双重检查锁定(Double-Checked Locking):

双重检查锁定是一种优化技术,旨在减少使用同步的开销。它首先检查实例是否已经存在,如果不存在才进行同步。但是,在Java中,双重检查锁定需要正确地使用volatile关键字来确保可见性和禁止指令重排

java 复制代码
 public class Singleton {  
    // volatile 保证多线程正确处理 instance 的可见性和禁止指令重排  
    private volatile static Singleton instance;  
  
    private Singleton() {}  
  
    public static Singleton getInstance() {  
        if (instance == null) {  
            synchronized (Singleton.class) {  
                if (instance == null) {  
                    instance = new Singleton();  
                }  
            }  
        }  
        return instance;  
    }  
}

在Java 5及以上版本中,双重检查锁定是可行的,但务必注意volatile关键字的正确使用。

静态内部类:

静态内部类实现单例模式的方式也是线程安全的。由于JVM的类加载机制保证了静态内部类只会被加载一次,从而保证了线程安全性。

java 复制代码
public class Singleton {
    private Singleton() {}

    // 使用静态内部类来创建单例,也是线程安全的
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

枚举:

在Java中,枚举类型是线程安全的,并且能防止反序列化重新创建新的对象,所以使用枚举实现单例模式也是线程安全的最佳实践。

java 复制代码
public enum Singleton {
    INSTANCE;

    public void whateverMethod() {
        // method body
    }
}

通过枚举实现单例模式不仅简单,而且自动支持序列化机制,防止反序列化重新创建新的对象。

初始化时同步(Initialization on Demand Holder, IODH):

这种方式结合了饿汉式和双重检查锁定的思想,但在实现上更为复杂,且在某些JVM上可能并不是线程安全的。因此,它不如上面提到的方法常用。

在选择单例模式的实现方式时,应该考虑代码的简洁性、性能需求以及线程安全性。通常情况下,使用枚举或静态内部类实现单例模式是比较推荐的方式,因为它们不仅线程安全,而且实现简单,易于理解。

实际例子

日志系统

假设我们正在开发一个日志记录系统,我们想要确保整个应用程序中只有一个日志记录器实例,这样我们就可以保持日志的一致性和管理方便。

java 复制代码
public class Logger {
    // 私有构造方法,防止外部通过new创建实例
    private Logger() {
        // 初始化代码
        System.out.println("Logger is being initialized...");
    }

    // 静态内部类,持有单例的引用
    private static class LoggerHolder {
        // 静态初始化器,保证线程安全
        private static final Logger INSTANCE = new Logger();
    }

    // 获取单例的公共静态方法
    public static Logger getInstance() {
        // 返回LoggerHolder中持有的单例引用
        return LoggerHolder.INSTANCE;
    }

    // 日志记录方法
    public void log(String message) {
        System.out.println("[" + System.currentTimeMillis() + "] " + message);
    }

    // 示例:使用单例模式的Logger类
    public static void main(String[] args) {
        // 获取Logger单例
        Logger logger = Logger.getInstance();
        // 使用Logger实例记录日志
        logger.log("This is a log message.");

        // 尝试再次获取Logger实例,应该是同一个
        Logger anotherLogger = Logger.getInstance();
        // 验证两个引用是否指向同一对象
        System.out.println("Are loggers the same? " + (logger == anotherLogger));
    }
}

数据库连接池

我们手撸一个最简单的数据库连接池,保证在并发情况下的单例访问。

java 复制代码
import java.sql.Connection;  
import java.sql.DriverManager;  
import java.sql.SQLException;  
import java.util.concurrent.ConcurrentHashMap;  
  
public class ConnectionPoolManager {  
    // 静态变量持有单例引用  
    private static ConnectionPoolManager instance;  
    // 数据库连接池  
    private final ConcurrentHashMap<String, Connection> pool;  
    // 初始化连接池的大小  
    private static final int MAX_CONNECTIONS = 10;  
    // 当前已分配的连接数  
    private int currentConnections = 0;  
    // 数据库连接信息  
    private static final String DB_URL = "jdbc:mysql://localhost:3306/mydb";  
    private static final String DB_USER = "username";  
    private static final String DB_PASSWORD = "password";  
  
    // 私有构造方法,防止外部通过new创建实例  
    private ConnectionPoolManager() {  
        this.pool = new ConcurrentHashMap<>();  
        // 初始化连接池,预先创建一些连接  
        for (int i = 0; i < MAX_CONNECTIONS; i++) {  
            try {  
                Connection connection = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);  
                pool.put("connection" + i, connection);  
            } catch (SQLException e) {  
                e.printStackTrace();  
            }  
        }  
    }  
  
    // 从连接池中获取一个连接  
    public synchronized Connection getConnection() {  
        if (currentConnections < MAX_CONNECTIONS) {  
            // 尝试从连接池中获取一个连接  
            for (String key : pool.keySet()) {  
                Connection connection = pool.get(key);  
                if (connection != null && !connection.isClosed()) {  
                    pool.remove(key); // 从连接池中移除,表示该连接已被使用  
                    currentConnections++;  
                    return connection;  
                }  
            }  
            // 如果连接池中没有可用连接,则创建新连接(此处简化处理,实际应检查是否超过最大连接数)  
            try {  
                Connection connection = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);  
                currentConnections++;  
                return connection;  
            } catch (SQLException e) {  
                e.printStackTrace();  
            }  
        }  
        return null; // 如果没有可用连接,返回null  
    }  
  
    // 回收一个连接到连接池  
    public synchronized void releaseConnection(Connection connection) {  
        if (connection != null && !connection.isClosed()) {  
            pool.put("connection" + (MAX_CONNECTIONS - currentConnections), connection);  
            currentConnections--;  
        }  
    }  
  
    // 获取单例的公共静态方法,使用双重检查锁定确保线程安全  
    public static ConnectionPoolManager getInstance() {  
        if (instance == null) {  
            synchronized (ConnectionPoolManager.class) {  
                if (instance == null) {  
                    instance = new ConnectionPoolManager();  
                }  
            }  
        }  
        return instance;  
    }  
  
    // 示例:使用单例模式的ConnectionPoolManager类  
    public static void main(String[] args) {  
        // 获取连接池管理器单例  
        ConnectionPoolManager connectionPoolManager = ConnectionPoolManager.getInstance();  
          
        // 从连接池获取一个连接  
        Connection connection = connectionPoolManager.getConnection();  
        if (connection != null) {  
            // 使用连接执行数据库操作...  
            System.out.println("Got a connection from the pool.");  
            // 假设使用完连接后释放回连接池  
            connectionPoolManager.releaseConnection(connection);  
            System.out.println("Released the connection back to the pool.");  
        } else {  
            System.out.println("No connections available in the pool.");  
        }  
    }  
}

在这个例子中,ConnectionPoolManager 类负责管理一个数据库连接池。它使用了 ConcurrentHashMap 来存储连接,以便能够高效地处理并发请求。getConnection 方法尝试从连接池中获取一个可用连接,如果没有可用连接则尝试创建一个新连接(这里简化了处理逻辑,实际中可能需要考虑连接数是否已经达到最大值)。releaseConnection 方法则将使用完毕的连接回收回连接池。

相关推荐
wrx繁星点点3 小时前
状态模式(State Pattern)详解
java·开发语言·ui·设计模式·状态模式
金池尽干5 小时前
设计模式之——观察者模式
观察者模式·设计模式
也无晴也无风雨5 小时前
代码中的设计模式-策略模式
设计模式·bash·策略模式
捕鲸叉14 小时前
MVC(Model-View-Controller)模式概述
开发语言·c++·设计模式
wrx繁星点点15 小时前
享元模式:高效管理共享对象的设计模式
java·开发语言·spring·设计模式·maven·intellij-idea·享元模式
凉辰15 小时前
设计模式 策略模式 场景Vue (技术提升)
vue.js·设计模式·策略模式
菜菜-plus15 小时前
java设计模式之策略模式
java·设计模式·策略模式
暗黑起源喵15 小时前
设计模式-迭代器
设计模式
lexusv8ls600h17 小时前
微服务设计模式 - 网关路由模式(Gateway Routing Pattern)
spring boot·微服务·设计模式
sniper_fandc19 小时前
抽象工厂模式
java·设计模式·抽象工厂模式