一、单例模式定义
单例设计模式(Singleton Pattern)是一种常用的创建型设计模式,其目的是 确保一个类在系统中只有一个实例,并提供一个全局访问点来获取这个唯一实例。
它常用于管理共享资源,如线程池、数据库连接池、配置对象、日志对象等。
- 只有一个实例:类在系统中只能有一个实例存在。
- 自行实例化:类自己创建并持有这个实例。
- 对外提供访问点:通过一个静态方法让外部获取这个唯一实例。
二、单例模式的结构
类图如下:
+-----------------+
| Singleton |
+-----------------+
| - instance | 静态私有变量,保存唯一实例
| + getInstance() | 静态公有方法,返回实例
+-----------------+
三、实现方式
1. 饿汉式(线程安全,类加载时创建实例)
public class Singleton {
// 在类加载时就初始化实例
//在类加载时就会创建该实例,因为 Java 类加载是线程安全的,且是在类加载阶段就完成初始化的,因此不需要同步机制
private static final Singleton instance = new Singleton();
// 私有构造器,防止外部直接实例化
private Singleton() {}
// 提供公共的获取实例的方法
public static Singleton getInstance() {
return instance;
}
}
优点: 简单、线程安全。
缺点: 类加载时就创建实例,可能浪费资源(如果没用到的话)。
2. 懒汉式(延迟加载,非线程安全)
/**
* 懒汉式单例模式的实现方式与饿汉式单例不同,它不在类加载时就创建实例,而是在第一次访问时才创建。
* 懒汉式单例模式(Lazy Initialization Singleton)是单例模式的一种实现方式,它延迟实例化对象,直到需要使用该对象时才进行创建。
* 这种方式适用于那些在应用启动时不确定是否需要某个对象的场景。
* 懒汉模式的关键点是 实例化延迟,即 第一次使用时才创建实例。
*
* 存在问题:
* 当多个线程同时调用 getInstance() 方法时,可能会创建多个实例。
* 为了保证线程安全,可以使用 synchronized 关键字来加锁,确保在多线程环境下只有一个线程能访问实例化过程。
*
*/
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton(); // 多线程下可能创建多个实例
}
return instance;
}
}
优点: 延迟创建实例,节省资源。
缺点: 多线程下不安全。
3. 懒汉式(线程安全,加锁)
public class Singleton {
// 使用 volatile 保证实例的可见性
private static volatile Singleton instance;
// 私有构造器,防止外部直接实例化
private Singleton() {}
// 使用 synchronized 确保线程安全
public static synchronized Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton(); // 延迟实例化
}
}
}
return instance;
}
}
缺点: 每次调用都要加锁,性能较差。
4. 双重检查锁(DCL,推荐方式)
public class Singleton {
// 使用 volatile 保证实例的可见性
// 即一个线程修改了实例变量,其他线程能够立刻看到修改后的值。
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;
}
}
优点: 线程安全、延迟加载、效率高。
5. 静态内部类(推荐方式)
public class Singleton {
private Singleton() {}
private static class Holder {
private static final Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return Holder.instance;
}
}
优点: 利用类加载机制保证线程安全和延迟初始化,无需加锁,效率高。
四、适用场景
- 配置对象管理(如全局配置读取器)
- 资源控制器(如数据库连接池)
- 日志工具类
- 操作系统文件系统、窗口管理器等只有一个实例的系统服务
五、优缺点总结
优点 |
缺点 |
控制实例数量,节省资源 |
不易扩展,不利于单元测试 |
全局访问点,便于管理 |
多线程实现复杂(尤其懒加载) |
避免频繁创建销毁对象,提高效率 |
违背面向对象的开放-封闭原则(难以继承) |
六、使用示例
假设我们有一个 日志管理系统,它需要使用单例模式来确保只有一个日志实例,
并通过代理模式来控制对日志写入的访问(例如添加额外的日志验证、权限检查等)。
public interface Logger {
void log(String message);
}
public class RealLogger implements Logger {
@Override
public void log(String message) {
System.out.println("日志记录: " + message);
}
}
public class LoggerSingleton {
// 使用饿汉式单例,实例在类加载时创建
private static final RealLogger instance = new RealLogger();
private LoggerSingleton() {
// 私有化构造器,防止外部创建实例
}
public static RealLogger getInstance() {
return instance;
}
}
public class LoggerProxy implements Logger {
private final RealLogger realLogger;
public LoggerProxy() {
// 获取单例对象
this.realLogger = LoggerSingleton.getInstance();
}
@Override
public void log(String message) {
// 在执行真实日志写入之前,添加一些额外的操作
checkPermissions();
formatMessage(message);
// 调用真实日志写入
realLogger.log(message);
}
private void checkPermissions() {
// 模拟权限检查
System.out.println("检查日志写入权限...");
// 假设权限检查通过
System.out.println("权限验证通过。");
}
private void formatMessage(String message) {
// 模拟格式化日志
System.out.println("格式化日志信息...");
}
}
public class Client {
public static void main(String[] args) {
// 使用代理来记录日志
Logger logger = new LoggerProxy();
// 记录日志
logger.log("这是一个代理模式和单例模式的示例!");
}
}