Java单例模式详解与实现方式

以下内容由AI生成,仅作参考与记录

单例模式(Singleton Pattern)是 Java 中最常用的设计模式之一,属于创建型模式。它的核心目标是确保一个类只有一个实例,并提供该实例的全局访问点


核心思想

  1. 私有化构造方法:禁止通过 new 创建实例。
  2. 静态私有成员变量:保存类的唯一实例。
  3. 静态公有方法:提供获取唯一实例的入口。

常见实现方式

1. 饿汉式(Eager Initialization)

在类加载时直接创建实例,线程安全但可能浪费资源。

public class Singleton {
    private static final Singleton instance = new Singleton();
    
    private Singleton() {} // 私有构造
    
    public static Singleton getInstance() {
        return instance;
    }
}
2. 懒汉式(Lazy Initialization)

延迟实例化,但需处理多线程安全问题。

public class Singleton {
    private static Singleton instance;
    
    private Singleton() {}
    
    // 方法加锁(影响性能)
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
3. 双重检查锁(Double-Checked Locking)

减少同步开销,线程安全且高效。

public class Singleton {
    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;
    }
}
4. 静态内部类(Static Inner Class)

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

public class Singleton {
    private Singleton() {}
    
    private static class Holder {
        static final Singleton INSTANCE = new Singleton();
    }
    
    public static Singleton getInstance() {
        return Holder.INSTANCE;
    }
}
5. 枚举(Enum,推荐方式)

简洁、线程安全、防止反射攻击,是《Effective Java》推荐的方式。

public enum Singleton {
    INSTANCE; // 单例实例
    
    public void doSomething() {
        // 业务逻辑
    }
}

应用场景

  • 需要频繁创建销毁的对象(如线程池、数据库连接池)。
  • 全局配置管理类(如读取配置文件)。
  • 工具类(无状态的工具方法)。

注意事项

  1. 线程安全:确保多线程环境下单例的唯一性。
  2. 反射攻击:通过反射可以破坏单例,需额外处理(如枚举天然防御)。
  3. 序列化问题:反序列化可能生成新对象,需实现 readResolve() 方法。
  4. 避免滥用:单例模式会引入全局状态,增加代码耦合度。

为什么推荐枚举实现?

  • 天然防止反射和序列化破坏单例。
  • 代码简洁,无复杂同步逻辑。
  • 由 JVM 保证线程安全。

枚举实现单例模式不仅简洁安全,还能通过丰富的代码结构实现复杂逻辑。以下是不同场景下的具体示例:


示例 1:基础工具类单例

实现一个全局的日期格式化工具类,提供线程安全的日期格式化方法:

public enum DateUtils {
    INSTANCE;

    private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public String format(Date date) {
        return sdf.format(date);
    }

    public Date parse(String dateStr) throws ParseException {
        return sdf.parse(dateStr);
    }
}

// 使用示例
String dateStr = DateUtils.INSTANCE.format(new Date());

示例 2:带初始化的配置管理单例

单例加载配置文件并缓存配置信息:

public enum ConfigManager {
    INSTANCE;

    private Properties config;

    // 枚举构造方法默认私有,在类加载时执行
    ConfigManager() {
        try (InputStream is = getClass().getResourceAsStream("/app.properties")) {
            config = new Properties();
            config.load(is);
        } catch (IOException e) {
            throw new RuntimeException("Failed to load config", e);
        }
    }

    public String getProperty(String key) {
        return config.getProperty(key);
    }
}

// 使用示例
String dbUrl = ConfigManager.INSTANCE.getProperty("database.url");

示例 3:实现接口的日志单例

单例实现日志接口,灵活扩展功能:

public interface Logger {
    void log(String message);
}

public enum FileLogger implements Logger {
    INSTANCE;

    private File logFile;

    FileLogger() {
        logFile = new File("app.log");
    }

    @Override
    public void log(String message) {
        try (FileWriter writer = new FileWriter(logFile, true)) {
            writer.write(message + "\n");
        } catch (IOException e) {
            System.err.println("Log failed: " + e.getMessage());
        }
    }
}

// 使用示例
FileLogger.INSTANCE.log("User logged in at " + new Date());

示例 4:数据库连接池管理

单例管理数据库连接池,确保全局唯一资源:

public enum ConnectionPool {
    INSTANCE;

    private HikariDataSource dataSource;

    ConnectionPool() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
        config.setUsername("root");
        config.setPassword("password");
        dataSource = new HikariDataSource(config);
    }

    public Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }

    public void shutdown() {
        if (dataSource != null) {
            dataSource.close();
        }
    }
}

// 使用示例
Connection conn = ConnectionPool.INSTANCE.getConnection();

示例 5:支持多方法的缓存管理

单例作为全局缓存,支持多种操作:

public enum CacheManager {
    INSTANCE;

    private final 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);
    }

    public void remove(String key) {
        cache.remove(key);
    }

    public void clear() {
        cache.clear();
    }
}

// 使用示例
CacheManager.INSTANCE.put("user:1001", "Alice");
String user = (String) CacheManager.INSTANCE.get("user:1001");

关键优势总结

  1. 防反射攻击:枚举实例由 JVM 保证唯一性,无法通过反射创建新实例。
  2. 线程安全:枚举实例在类加载时初始化,天然线程安全。
  3. 序列化安全:默认处理序列化和反序列化,无需额外代码。
  4. 代码简洁:无需手动处理双重检查锁或同步问题。

适用场景

  • 需要严格全局唯一性的组件(如配置管理、资源池)。
  • 工具类或服务类(如日志、缓存、线程池)。
  • 希望避免反射/序列化破坏单例的场景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值