单例模式
单例模式是一种创建型设计模式,确保一个类只有一个实例,并提供全局访问点。
核心特点
- 唯一实例:整个应用程序中只能存在一个实例
- 全局访问:提供全局访问点来获取这个实例
- 自我管理:类自己负责创建和管理自己的实例
基本结构
- 私有构造函数(防止外部创建实例)
- 私有静态变量(存储唯一实例)
- 公有静态方法(提供全局访问点)
饿汉式(Eager Initialization)
优点:简单、线程安全 缺点:可能浪费内存(不管用不用都创建)
java
// ===== 1. 饿汉式单例 =====
class EagerSingleton {
// Java 的类加载机制确保每个类在 JVM 中只会被加载一次
// 类加载时就创建实例 这个加载式项目启动加载一次 所以是单例
private static final EagerSingleton INSTANCE = new EagerSingleton();
private EagerSingleton() {
System.out.println("饿汉式单例创建");
}
public static EagerSingleton getInstance() {
return INSTANCE;
}
}懒汉式(Lazy Initialization)
优点:按需创建,节省内存 缺点:基础版本线程不安全
java
// ===== 2. 懒汉式单例(线程不安全)=====
class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {
System.out.println("懒汉式单例创建");
}
// 这个只有访问时才会初始化LazySingleton
// 未初始化时,如果并发访问时多个线程进入此方法,这时候都会去new LazySingleton(); 线程不安全
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}线程安全懒汉式
优点:线程安全 缺点:每次获取实例都要同步,性能差
java
// ===== 3. 线程安全的懒汉式 =====
class ThreadSafeLazySingleton {
private static ThreadSafeLazySingleton instance;
private ThreadSafeLazySingleton() {
System.out.println("线程安全懒汉式单例创建");
}
// synchronized 锁住
// 其实只要锁住初始化就可以保证单例模式了,现在不管时初始化还是获取instance都被锁住了造成时间上的浪费
public static synchronized ThreadSafeLazySingleton getInstance() {
if (instance == null) {
instance = new ThreadSafeLazySingleton();
}
return instance;
}
}双重检查锁(Double-Checked Locking)
优点:延迟加载 + 线程安全 + 性能好 缺点:实现复杂,需要 volatile
java
// ===== 4. 双重检查锁 =====
class DoubleCheckSingleton {
private static volatile DoubleCheckSingleton instance;
private DoubleCheckSingleton() {
System.out.println("双重检查锁单例创建");
}
public static DoubleCheckSingleton getInstance() {
if (instance == null) {
synchronized (DoubleCheckSingleton.class) {
if (instance == null) {
instance = new DoubleCheckSingleton();
}
}
}
return instance;
}
}为什么双重检查锁需要 volatile?
问题:指令重排序
java
instance = new Singleton(); // 这行代码实际上包含3个步骤:
// 1、分配内存空间
// 2、初始化对象
// 3、将引用指向内存空间
// 危险的重排序:编译器可能会将步骤2和3重排序导致的问题
java
// 线程A执行到这里
instance = new Singleton(); // 已经赋值但还没初始化完成,原始时这里重排序了,2、3布置置换了,所以先赋值,在初始化
// 线程B同时执行
if (instance == null) { // false!因为已经赋值了
// 跳过同步块
}
return instance; // 返回一个没有完全初始化的对象!volatile 的作用:
shell
# volatile 是 Java 中的一个关键字,用来解决多线程环境下的可见性和有序性问题。
# 1、禁止重排序:确保初始化完成后才赋值
# 2、保证可见性:一个线程的修改立即对其他线程可见静态内部类(推荐)
优点:延迟加载 + 线程安全 + 实现简单 缺点:无明显缺点
java
// ===== 5. 静态内部类(推荐)=====
class StaticInnerSingleton {
private StaticInnerSingleton() {
System.out.println("静态内部类单例创建");
}
private static class SingletonHolder {
private static final StaticInnerSingleton INSTANCE = new StaticInnerSingleton();
}
public static StaticInnerSingleton getInstance() {
return SingletonHolder.INSTANCE;
}
}对象创建: 只在 SingletonHolder 类第一次加载时创建,仅此一次
后续调用: 返回的都是同一个对象的引用
内存开销: 只有一个 Singleton 实例存在于堆中
