Java 单例模式:确保一个类只有一个实例

单例模式是最常用、最重要的设计模式之一。它确保一个类在整个应用中只有一个实例,并提供一个全局访问点。

为什么需要单例?

场景:数据库连接池

// 如果没有单例,每个地方都 new 一个连接池
DatabasePool pool1 = new DatabasePool();
DatabasePool pool2 = new DatabasePool();
// 结果:创建了多个连接池,浪费资源,连接数超标

// 使用单例,整个应用只有一个连接池
DatabasePool pool = DatabasePool.getInstance();
// 无论哪里获取,都是同一个实例

单例模式的核心要求

  1. 一个类只能有一个实例
  2. 必须自行创建这个实例
  3. 必须向整个系统提供这个实例

🔧 实现单例的 5 种方式

1. 饿汉式(Eager Initialization) - 最简单

特点:类加载时就创建实例

public class Singleton {
    // 1. 私有静态实例,类加载时就创建
    private static final Singleton INSTANCE = new Singleton();
    
    // 2. 私有构造器,防止外部 new
    private Singleton() {
        System.out.println("饿汉式单例被创建");
    }
    
    // 3. 公共静态方法,返回唯一实例
    public static Singleton getInstance() {
        return INSTANCE;
    }
    
    // 业务方法
    public void showMessage() {
        System.out.println("我是饿汉式单例");
    }
}

// 使用
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2);  // true,是同一个对象

优点:简单,线程安全

缺点:即使不用也会创建,可能浪费内存

2. 懒汉式(Lazy Initialization) - 延迟加载

特点:第一次使用时才创建实例

2.1 线程不安全的懒汉式(❌ 有问题)
public class Singleton {
    private static Singleton instance;
    
    private Singleton() {}
    
    public static Singleton getInstance() {
        if (instance == null) {  // 线程1判断为null
            instance = new Singleton();  // 线程2也判断为null,会创建第二个实例
        }
        return instance;
    }
}
// ❌ 多线程环境下可能创建多个实例
2.2 线程安全的懒汉式(方法同步)
public class Singleton {
    private static Singleton instance;
    
    private Singleton() {}
    
    // 使用 synchronized 保证线程安全
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
// ✅ 线程安全
// ❌ 每次获取都要同步,性能差

3. 双重检查锁(Double-Checked Locking)- 推荐

特点:只在第一次创建时同步,兼顾性能和线程安全

public class Singleton {
    // 必须使用 volatile 防止指令重排序
    private static volatile Singleton instance;
    
    private Singleton() {
        System.out.println("双重检查锁单例被创建");
    }
    
    public static Singleton getInstance() {
        // 第一次检查:避免不必要的同步
        if (instance == null) {
            // 同步代码块
            synchronized (Singleton.class) {
                // 第二次检查:确保只有一个线程能创建实例
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
    
    public void showMessage() {
        System.out.println("我是双重检查锁单例");
    }
}

工作原理

线程A调用 getInstance() 
  ↓
if (instance == null)  // 第一次检查
  ↓
进入 synchronized 块
  ↓
if (instance == null)  // 第二次检查
  ↓
创建实例
  ↓
返回实例

线程B调用 getInstance()
  ↓
if (instance == null)  // 第一次检查,发现不为null
  ↓
直接返回已有实例

为什么需要 volatile?

instance = new Singleton();  // 这行代码分3步:
// 1. 分配内存空间
// 2. 初始化对象
// 3. 将instance指向内存地址
// 没有volatile,JVM可能重排序为1-3-2
// 导致其他线程拿到未初始化的对象

4. 静态内部类(Static Inner Class)- 最佳实现

特点:利用类加载机制保证线程安全,实现延迟加载

public class Singleton {
    
    private Singleton() {
        System.out.println("静态内部类单例被创建");
    }
    
    // 静态内部类
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
    
    public void showMessage() {
        System.out.println("我是静态内部类单例");
    }
}

原理

  • 外部类加载时,内部类不会加载
  • 调用 getInstance()时,才会加载内部类
  • 类加载是线程安全的,由 JVM 保证
  • 实现了延迟加载 + 线程安全

这是最推荐的单例实现方式!

5. 枚举(Enum)- 最安全

特点:JVM 保证线程安全,防止反射和序列化攻击

public enum Singleton {
    INSTANCE;  // 唯一的实例
    
    // 可以添加方法
    public void showMessage() {
        System.out.println("我是枚举单例");
    }
    
    // 可以添加属性
    private String data = "单例数据";
    
    public String getData() {
        return data;
    }
    
    public void setData(String data) {
        this.data = data;
    }
}

// 使用
Singleton singleton = Singleton.INSTANCE;
singleton.showMessage();
System.out.println(singleton.getData());

优点

  • 简单
  • 线程安全
  • 防止反射攻击
  • 防止序列化破坏单例
  • 防止克隆破坏单例

单例模式的应用场景

场景1:配置管理器

public class ConfigManager {
    private static volatile ConfigManager instance;
    private Properties config;
    
    private ConfigManager() {
        loadConfig();
    }
    
    public static ConfigManager getInstance() {
        if (instance == null) {
            synchronized (ConfigManager.class) {
                if (instance == null) {
                    instance = new ConfigManager();
                }
            }
        }
        return instance;
    }
    
    private void loadConfig() {
        config = new Properties();
        try (InputStream input = getClass().getClassLoader()
                .getResourceAsStream("config.properties")) {
            config.load(input);
        } catch (IOException e) {
            throw new RuntimeException("加载配置文件失败", e);
        }
    }
    
    public String getProperty(String key) {
        return config.getProperty(key);
    }
    
    public int getIntProperty(String key) {
        return Integer.parseInt(config.getProperty(key));
    }
}

// 使用
String dbUrl = ConfigManager.getInstance().getProperty("database.url");
int timeout = ConfigManager.getInstance().getIntProperty("timeout");

场景2:数据库连接池

public class DatabasePool {
    private static DatabasePool instance;
    private List<Connection> connections;
    
    private DatabasePool() {
        // 初始化连接池
        connections = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            connections.add(createConnection());
        }
    }
    
    public static DatabasePool getInstance() {
        if (instance == null) {
            synchronized (DatabasePool.class) {
                if (instance == null) {
                    instance = new DatabasePool();
                }
            }
        }
        return instance;
    }
    
    public Connection getConnection() {
        // 从连接池获取连接
        synchronized (connections) {
            if (!connections.isEmpty()) {
                return connections.remove(0);
            }
        }
        // 连接池为空,创建新连接
        return createConnection();
    }
    
    public void releaseConnection(Connection conn) {
        // 释放连接回到连接池
        synchronized (connections) {
            if (connections.size() < 10) {
                connections.add(conn);
            } else {
                // 关闭多余的连接
                closeConnection(conn);
            }
        }
    }
    
    private Connection createConnection() {
        // 创建数据库连接
        return null; // 实际实现
    }
    
    private void closeConnection(Connection conn) {
        // 关闭连接
    }
}

场景3:日志记录器

public class Logger {
    private static Logger instance;
    private File logFile;
    private FileWriter writer;
    
    private Logger() {
        try {
            logFile = new File("app.log");
            writer = new FileWriter(logFile, true);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    public static Logger getInstance() {
        if (instance == null) {
            synchronized (Logger.class) {
                if (instance == null) {
                    instance = new Logger();
                }
            }
        }
        return instance;
    }
    
    public void log(String message) {
        try {
            writer.write(LocalDateTime.now() + " - " + message + "\n");
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    public void close() {
        try {
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

场景4:缓存管理器

public class CacheManager {
    private static CacheManager instance;
    private Map<String, Object> cache;
    
    private CacheManager() {
        cache = new ConcurrentHashMap<>();
    }
    
    public static CacheManager getInstance() {
        if (instance == null) {
            synchronized (CacheManager.class) {
                if (instance == null) {
                    instance = new CacheManager();
                }
            }
        }
        return instance;
    }
    
    public void put(String key, Object value) {
        cache.put(key, value);
    }
    
    public Object get(String key) {
        return cache.get(key);
    }
    
    public void remove(String key) {
        cache.remove(key);
    }
    
    public void clear() {
        cache.clear();
    }
}

单例模式的问题和解决方案

问题1:反射攻击

反射可以调用私有构造器创建新实例

public class ReflectionAttackTest {
    public static void main(String[] args) throws Exception {
        Singleton s1 = Singleton.getInstance();
        
        // 通过反射创建第二个实例
        Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        Singleton s2 = constructor.newInstance();
        
        System.out.println(s1 == s2);  // false,单例被破坏了!
    }
}

解决方案

public class Singleton {
    private static volatile Singleton instance;
    private static boolean flag = false;
    
    private Singleton() {
        // 防止反射攻击
        if (flag) {
            throw new RuntimeException("单例模式禁止反射创建");
        }
        flag = true;
    }
    
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

问题2:序列化破坏

序列化和反序列化会创建新实例

public class SerializationAttackTest {
    public static void main(String[] args) throws Exception {
        Singleton s1 = Singleton.getInstance();
        
        // 序列化
        ObjectOutputStream oos = new ObjectOutputStream(
            new FileOutputStream("singleton.ser")
        );
        oos.writeObject(s1);
        oos.close();
        
        // 反序列化
        ObjectInputStream ois = new ObjectInputStream(
            new FileInputStream("singleton.ser")
        );
        Singleton s2 = (Singleton) ois.readObject();
        ois.close();
        
        System.out.println(s1 == s2);  // false,单例被破坏了!
    }
}

解决方案

public class Singleton implements Serializable {
    private static final long serialVersionUID = 1L;
    private static volatile Singleton instance;
    
    private Singleton() {}
    
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
    
    // 防止序列化破坏
    protected Object readResolve() {
        return getInstance();  // 返回已有实例
    }
}

问题3:克隆破坏

clone() 方法会创建新实例

public class Singleton implements Cloneable {
    // ... 单例实现
    
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();  // ❌ 这样会破坏单例
    }
    
    // ✅ 正确:禁止克隆
    @Override
    protected Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException("单例模式禁止克隆");
    }
}

线程安全的单例模式对比

实现方式 线程安全 延迟加载 防止反射 防止序列化 性能
饿汉式
懒汉式(同步方法)
双重检查锁
静态内部类
枚举

最佳实践建议

1. 选择哪种实现?

// 1. 如果确定一定会用到,用饿汉式
public class EarlySingleton {
    private static final EarlySingleton INSTANCE = new EarlySingleton();
}

// 2. 大多数情况,用静态内部类(推荐!)
public class InnerClassSingleton {
    private static class Holder {
        static final InnerClassSingleton INSTANCE = new InnerClassSingleton();
    }
}

// 3. 需要防止反射/序列化攻击,用枚举
public enum EnumSingleton {
    INSTANCE;
}

// 4. 双重检查锁用于复杂初始化
public class ComplexSingleton {
    private static volatile ComplexSingleton instance;
    private SomeResource resource;
    
    private ComplexSingleton() {
        resource = new SomeResource();  // 复杂的初始化
        resource.init();
    }
}

2. 在 Spring 中使用单例

@Component  // Spring 默认就是单例
@Scope("singleton")  // 显式声明
public class UserService {
    // Spring 容器管理的单例
}

// 或者使用 @Bean
@Configuration
public class AppConfig {
    @Bean
    @Scope("singleton")
    public DataSource dataSource() {
        return new HikariDataSource();
    }
}

3. 单例模式的替代方案

// 1. 使用依赖注入(推荐)
public class UserService {
    // 通过构造器注入
    private final DatabasePool pool;
    
    public UserService(DatabasePool pool) {
        this.pool = pool;
    }
}

// 2. 使用静态工具类
public final class StringUtils {
    private StringUtils() {}  // 私有构造器,防止实例化
    
    public static boolean isEmpty(String str) {
        return str == null || str.trim().isEmpty();
    }
}

单例模式常见面试题

1. 为什么要用单例模式?

  • 控制资源的使用(如连接池)
  • 控制实例的数量
  • 全局访问点
  • 节省内存和计算资源

2. 单例模式的缺点是什么?

  • 难以扩展(只能有一个实例)
  • 违背单一职责原则(控制自己+业务逻辑)
  • 多线程环境需要特殊处理
  • 单元测试困难(全局状态)

3. 如何实现线程安全的单例?

  • 使用 synchronized
  • 使用双重检查锁
  • 使用静态内部类
  • 使用枚举

4. 单例模式 vs 静态类

单例模式 静态类
可以有实例,可以继承 不能实例化,不能继承
可以实现接口 不能实现接口
可以延迟初始化 类加载时就初始化
可以序列化 不能序列化

实战练习

创建一个计数器单例,要求:

  1. 整个应用只有一个计数器实例
  2. 线程安全
  3. 可以获取当前计数
  4. 可以增加计数
  5. 可以重置计数
// 你的实现
public class Counter {
    // TODO: 实现线程安全的单例计数器
}

总结

单例模式的本质:控制实例数量,节省资源,提供全局访问。

选择指南

  • 简单场景 → 饿汉式
  • 一般场景 → 静态内部类(最推荐)
  • 防止攻击 → 枚举
  • 复杂初始化 → 双重检查锁

记住核心:单例模式的核心是私有构造器 + 静态获取方法。根据具体需求选择合适的实现方式,并注意线程安全和可能的安全攻击。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐