跳转到内容

Java单例模式

来自代码酷
Admin留言 | 贡献2025年4月30日 (三) 19:04的版本 (Page creation by admin bot)

(差异) ←上一版本 | 已核准修订 (差异) | 最后版本 (差异) | 下一版本→ (差异)

Java单例模式[编辑 | 编辑源代码]

单例模式(Singleton Pattern)是一种常用的软件设计模式,属于创建型模式的一种。它确保一个类只有一个实例,并提供一个全局访问点。这种模式通常用于控制资源访问,如配置管理、线程池、缓存、日志对象等。

介绍[编辑 | 编辑源代码]

单例模式的核心思想是限制类的实例化次数,确保在整个应用程序中只有一个实例存在。这样做的好处包括:

  • 资源节约:避免重复创建对象,减少内存开销。
  • 全局访问:提供一个统一的访问点,便于管理和维护。
  • 状态共享:所有使用者共享同一个实例的状态。

单例模式的关键实现要点: 1. 私有化构造函数,防止外部直接实例化。 2. 提供一个静态方法获取唯一实例。 3. 在多线程环境下保证线程安全。

实现方式[编辑 | 编辑源代码]

1. 饿汉式(Eager Initialization)[编辑 | 编辑源代码]

最简单的实现方式,在类加载时就创建实例。

public class EagerSingleton {
    // 类加载时就初始化
    private static final EagerSingleton instance = new EagerSingleton();
    
    // 私有构造函数
    private EagerSingleton() {}
    
    // 全局访问点
    public static EagerSingleton getInstance() {
        return instance;
    }
}

优点:实现简单,线程安全
缺点:即使不使用也会创建实例,可能造成资源浪费

2. 懒汉式(Lazy Initialization)[编辑 | 编辑源代码]

延迟实例化,在第一次使用时才创建实例。

public class LazySingleton {
    private static LazySingleton instance;
    
    private LazySingleton() {}
    
    public static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

优点:按需创建,节省资源
缺点:非线程安全,多线程环境下可能创建多个实例

3. 线程安全的懒汉式[编辑 | 编辑源代码]

通过同步方法保证线程安全。

public class ThreadSafeSingleton {
    private static ThreadSafeSingleton instance;
    
    private ThreadSafeSingleton() {}
    
    public static synchronized ThreadSafeSingleton getInstance() {
        if (instance == null) {
            instance = new ThreadSafeSingleton();
        }
        return instance;
    }
}

优点:线程安全
缺点:每次获取实例都需要同步,性能较差

4. 双重检查锁定(Double-Checked Locking)[编辑 | 编辑源代码]

优化后的线程安全实现,减少同步开销。

public class DCLSingleton {
    private volatile static DCLSingleton instance;
    
    private DCLSingleton() {}
    
    public static DCLSingleton getInstance() {
        if (instance == null) {
            synchronized (DCLSingleton.class) {
                if (instance == null) {
                    instance = new DCLSingleton();
                }
            }
        }
        return instance;
    }
}

注意:必须使用volatile关键字防止指令重排序

5. 静态内部类实现[编辑 | 编辑源代码]

利用类加载机制保证线程安全,同时实现延迟加载。

public class InnerClassSingleton {
    private InnerClassSingleton() {}
    
    private static class SingletonHolder {
        private static final InnerClassSingleton INSTANCE = new InnerClassSingleton();
    }
    
    public static InnerClassSingleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

优点:线程安全,延迟加载,实现简单

6. 枚举实现(推荐方式)[编辑 | 编辑源代码]

Joshua Bloch在《Effective Java》中推荐的方式。

public enum EnumSingleton {
    INSTANCE;
    
    public void doSomething() {
        // 实例方法
    }
}

优点:线程安全,防止反射攻击,自动支持序列化

类图[编辑 | 编辑源代码]

classDiagram class Singleton { -static instance: Singleton -Singleton() +getInstance() Singleton }

实际应用案例[编辑 | 编辑源代码]

1. 配置管理器[编辑 | 编辑源代码]

应用程序通常需要一个全局的配置管理器,单例模式非常适合这种场景。

public class ConfigManager {
    private static ConfigManager instance;
    private Properties configs;
    
    private ConfigManager() {
        // 加载配置文件
        configs = new Properties();
        try (InputStream input = getClass().getResourceAsStream("/config.properties")) {
            configs.load(input);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
    
    public static synchronized ConfigManager getInstance() {
        if (instance == null) {
            instance = new ConfigManager();
        }
        return instance;
    }
    
    public String getConfig(String key) {
        return configs.getProperty(key);
    }
}

2. 数据库连接池[编辑 | 编辑源代码]

数据库连接池通常只需要一个实例来管理所有连接。

public class ConnectionPool {
    private static ConnectionPool instance;
    private List<Connection> pool;
    
    private ConnectionPool() {
        // 初始化连接池
        pool = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            pool.add(createConnection());
        }
    }
    
    public static synchronized ConnectionPool getInstance() {
        if (instance == null) {
            instance = new ConnectionPool();
        }
        return instance;
    }
    
    public Connection getConnection() {
        // 获取可用连接逻辑
    }
    
    public void releaseConnection(Connection conn) {
        // 释放连接逻辑
    }
    
    private Connection createConnection() {
        // 创建新连接
    }
}

注意事项[编辑 | 编辑源代码]

1. 线程安全:在多线程环境中必须考虑线程安全问题 2. 序列化:如果需要序列化单例对象,必须实现readResolve方法防止反序列化创建新实例 3. 反射攻击:可以通过反射调用私有构造函数,需要额外防护 4. 单元测试:单例模式可能使单元测试变得困难,因为状态是共享的

性能考虑[编辑 | 编辑源代码]

不同实现方式的性能差异可以通过以下公式表示:

Taccess=Tlookup+{0饿汉式Tsync同步方法Tcheck+Pnull×Tsync双重检查锁定

其中:

  • Taccess 是获取实例的总时间
  • Tlookup 是访问静态变量的时间
  • Tsync 是同步开销
  • Tcheck 是空检查时间
  • Pnull 是实例为null的概率

最佳实践[编辑 | 编辑源代码]

1. 在Java 5+环境中,优先使用枚举实现 2. 如果需要延迟加载,考虑静态内部类实现 3. 避免使用反射破坏单例 4. 在依赖注入框架中,通常不需要手动实现单例

常见问题[编辑 | 编辑源代码]

Q: 单例模式是反模式吗? A: 单例模式有时被认为是一种反模式,因为它引入了全局状态,使代码难以测试。但在管理共享资源等场景下仍然有用。

Q: 如何测试单例类? A: 可以通过以下方式:

  • 提供重置实例的方法(仅用于测试)
  • 使用依赖注入
  • 将单例包装在可测试的接口后面

Q: 单例模式和静态类有什么区别? A: 主要区别:

  • 单例可以实现接口,可以继承
  • 单例可以延迟初始化
  • 单例可以序列化
  • 静态类所有方法都必须是静态的