1.定义
单例模式是一种创建型设计模式,它确保在jvm内存中,一个类的实例只有一个,并且对外提供一个可以访问它的静态方法。核心目的是做资源控制,如数据库连接池,全局配置类等,限制只有一个实例,避免资源浪费和导致资源冲突。
2.实现
- 饿汉式(最简单,最常用) :类加载到 JVM 时就创建好实例。优点 :绝对线程安全(类加载机制保证),没有锁,响应快。缺点:不管用不用,启动就占内存。
- 懒汉式(双检锁 DCL) :需要时才创建。使用volatile + synchronized 双重检查。
- 静态内部类(最优雅) :利用 JVM 类加载的懒加载机制。既能实现懒加载,又能保证线程安全,代码比 DCL 简洁。
3.应用场景
- 日志记录器:全系统使用同一个日志上下文实例,保证日志输出的格式、存储路径和异步刷盘策略统一。
- 配置管理:全局配置读取。
- 池化管理:数据库连接池,线程池。
4.优缺点
优点:
- 可以保证一个类只有一个实例。
- 获得了一个指向该实例的全局访问节点。
缺点:
- 违反了单一职责原则。该模式同时解决了两个问题。
- 单例模式可能掩盖不良设计, 比如程序各组件之间相互了解过多等。
- 该模式在多线程环境下需要进行特殊处理, 避免多个线程多次创建单例对象。
5.示例代码
饿汉式
java
public class SingletonEager {
// ★ 核心点1:类加载时即完成初始化,由JVM保证线程绝对安全
// 无需 volatile,因为 JVM 在类加载的初始化阶段(<clinit>() 方法)会保证静态变量的赋值顺序
// 不存在并发争抢,因此也不存在指令重排带来的可见性问题
private static final SingletonEager instance = new SingletonEager();
// 私有构造器,防止外部 new
private SingletonEager() {
// 防反射攻击(饿汉式同样适用)
// 由于类加载时 instance 已经不为 null,反射调用构造器时会被拦截
if (instance != null) {
throw new RuntimeException("请勿通过反射破坏单例");
}
}
public static SingletonEager getInstance() {
// ★ 核心点2:直接返回,无需任何同步检查或判空逻辑
// 调用时毫无性能开销,但若从未使用,资源依然被加载(类加载时即占用内存)
return instance;
}
}
懒汉式(双重检查锁)
java
public class SingletonDCL {
// ★ 核心点1:必须加 volatile,防止指令重排序
// 防止 instance = new SingletonDCL() 时,赋值操作先于对象初始化完成,导致其他线程拿到半成品对象
private static volatile SingletonDCL instance;
// 私有构造器,防止外部 new
private SingletonDCL() {
// 防反射攻击(可选,但加分项)
if (instance != null) {
throw new RuntimeException("请勿通过反射破坏单例");
}
}
public static SingletonDCL getInstance() {
// 第一重检查:避免不必要的同步(性能优化)
if (instance == null) {
// ★ 核心点2:类锁,保证线程安全
synchronized (SingletonDCL.class) {
// 第二重检查:防止在等待锁的过程中,被其他线程抢先创建了实例
if (instance == null) {
instance = new SingletonDCL();
}
}
}
return instance;
}
}
静态内部类:
java
public class SingletonHolder {
// 私有构造器
private SingletonHolder() {
// 防反射攻击
if (Holder.INSTANCE != null) {
throw new RuntimeException("请勿通过反射破坏单例");
}
}
// ★ 核心点:利用 JVM 类加载机制保证线程安全,且实现懒加载
// 只有调用 getInstance() 时,Holder 类才会被加载,INSTANCE 才会初始化
private static class Holder {
private static final SingletonHolder INSTANCE = new SingletonHolder();
}
public static SingletonHolder getInstance() {
return Holder.INSTANCE;
}
}