# 单例模式(Singleton Pattern)
# 概念
单例模式是一种创建型设计模式,确保一个类只有一个实例,并提供一个全局访问点。
# 作用
1.资源优化:避免频繁创建和销毁实例,节省系统资源,提高性能,常用于资源消耗较大的对象,如数据库连接池。
2.全局访问:提供全局访问点,方便在系统各处获取和使用实例,避免传递实例的繁琐。
3.控制实例创建 :严格控制实例的生成,确保某些关键业务逻辑只有一个入口。
# 场景
1.资源共享:如打印机、线程池等资源,避免资源冲突和浪费。
2.配置管理:系统配置信息,确保统一读取和管理配置。
3.日志记录:日志系统,集中管理日志记录,避免多实例导致混乱。
4.缓存管理:缓存系统,统一管理和操作缓存数据。
# 饿汉式(线程安全)
定义单例类:
package net.feixiang.creational.singleton.eager;
/**
* 饿汉式单例类
* 特点:类加载时立即初始化实例(非延迟加载)
*/
public class EagerSingleton {
// 静态常量在类加载时直接初始化实例
// final 保证实例不可变,static 保证instance类级别唯一性
// 线程安全:由 JVM 类加载机制保证(天然线程安全)
private static final EagerSingleton instance = new EagerSingleton();
/**
* 私有构造函数
* 作用:阻止外部通过 new 关键字创建实例
*/
private EagerSingleton() {}
/**
* 获取单例实例的全局访问点
* @return 已预先创建的单例实例
*/
public static EagerSingleton getInstance() {
// 直接返回类加载时已初始化的实例
return instance;
}
/**
* 示例业务方法
*/
public void doSomething() {
System.out.println("EagerSingleton:Hello FEIXIANG!");
}
}
运行示例:
package net.feixiang.creational.singleton.eager;
/**
* 调用饿汉式单例类的示例程序
*/
public class EagerSingletonDemo {
public static void main(String[] args) {
// 获取单例实例
EagerSingleton singleton = EagerSingleton.getInstance();
// 调用单例实例的方法
for (int i = 0; i < 3; i++) {
singleton.doSomething();
// 输出单例实例的哈希码以判断是否是同一个实例
System.out.println(singleton.hashCode());
}
}
}
控制台输出:
EagerSingleton:Hello FEIXIANG!
1694819250
EagerSingleton:Hello FEIXIANG!
1694819250
EagerSingleton:Hello FEIXIANG!
1694819250
# 解析
# 为什么叫“饿汉式”?
“饿汉式”这个名字来源于它的行为特点。在饿汉式单例模式中,实例在类加载时就被创建,就好像一个饥饿的人(饿汉)迫不及待地吃掉食物一样。它在类加载时就“吃掉”了资源,确保了实例的唯一性和线程安全性。
# 为什么线程安全?
饿汉式单例模式的线程安全性主要依赖于Java的类加载机制。在Java中,类的静态变量在类加载时被初始化,并且类加载过程是由JVM保证线程安全的。因此,当多个线程同时调用getInstance()方法时,由于实例已经在类加载时被创建,所以不会出现多次创建实例的问题。
# 怎么判断是同一个实例?
在示例代码中,通过输出单例实例的哈希码(hashCode)可以判断是否是同一个实例。由于饿汉式单例模式在类加载时就创建实例,因此多次调用getInstance()方法返回的都是同一个实例,其哈希码也是相同的。
# static
和final
的作用
static
用于声明一个静态变量instance
,它属于类本身而不是类的某个实例。静态变量在类加载时被初始化,并且在内存中只有一份副本,所有类的实例共享这个变量。
在单例模式中,static
确保instance
是类级别的变量,而不是实例级别的变量,这样可以保证只有一个实例存在。
final
用于声明一个最终变量instance
,它的值一旦被初始化后不能被修改。
在单例模式中,final
确保instance
只能被赋值一次,从而保证单例的唯一性。
结合使用的效果:
确保只有一个new Singleton()。通过static
和final
的结合,instance
在类加载时被初始化,并且只能被赋值一次。这样就确保了在整个应用程序中,只有一个Singleton
实例被创建。
# private EagerSingleton() {}
的作用
将构造方法声明为私有,防止外部通过new Singleton()的方式创建类的实例。
在单例模式中,我们希望类的实例只能通过getInstance()方法获取,而不是通过直接调用构造方法创建。
如果没有私有构造方法,外部代码可以通过new Singleton()创建多个实例,破坏单例模式的唯一性。
# public static EagerSingleton getInstance()
的作用
提供一个公共的静态方法,用于获取单例实例。
通过这个方法,外部代码可以访问唯一的单例实例,而不需要知道实例的创建细节。
# 懒汉式(线程不安全)
# 为什么需要懒汉式单例模式?
# 1.资源优化
如果实例创建成本较高(如数据库连接池、大型对象等),而实例可能在整个应用程序运行过程中都不被使用,那么饿汉式会在类加载时就创建实例,造成资源浪费。懒汉式则在第一次使用时才创建实例,避免了这种浪费。
# 2.延迟初始化
在某些情况下,实例的初始化可能依赖于某些运行时条件或配置。懒汉式可以在满足这些条件时才初始化实例,从而提高系统的灵活性。
# 3.提高启动速度
如果实例的初始化过程较复杂且耗时,而应用程序在启动时并不立即需要该实例,那么懒汉式可以延迟初始化,从而提高应用程序的启动速度。
# 懒汉式单例模式示例
/**
* 懒汉式单例类(线程不安全)
* 特点:延迟初始化(在第一次使用时创建实例)
*/
public class LazySingleton1 {
// 静态变量用于保存唯一实例(未直接初始化)
private static LazySingleton1 instance;
/**
* 私有构造函数
* 作用:阻止外部通过 new 关键字直接创建实例
*/
private LazySingleton1() {}
/**
* 获取单例实例的全局访问点(可能存在多线程同时访问的情况)
* @return 单例实例
*/
public static LazySingleton1 getInstance() {
// 使用时才检查实例是否已创建,未创建则创建实例再返回
if (instance == null) {
instance = new LazySingleton1();
}
return instance;
}
/**
* 示例业务方法
*/
public void doSomething() {
System.out.println("LazySingleton1:Hello FEIXIANG!");
}
}
# 为什么懒汉式单例类是线程不安全的
上面方法是线程不安全的,因为在多线程环境下,可能会有多个线程同时通过 if (instance == null) 检查,从而导致多次创建 Singleton 实例。
示例说明
假设两个线程同时调用 getInstance() 方法:
1.线程 A 和 线程 B 同时调用 getInstance()。
2.线程 A 和 线程 B 同时检查 if (instance == null),发现 instance 为 null。
3.线程 A 和 线程 B 都进入 if 块,分别创建 Singleton 实例。
4.最终,Singleton 类被创建了两次,破坏了单例模式的唯一性。
如何处理线程安全问题
为了确保线程安全,可以使用 synchronized 关键字来同步 getInstance() 方法。这样可以确保在同一时间只有一个线程可以进入 getInstance() 方法,从而避免多个线程同时创建实例。
详细解释
synchronized 关键字:
- synchronized 用于确保同一时间只有一个线程可以进入被修饰的方法或代码块。
- 在 getInstance() 方法上使用 synchronized,可以确保在多线程环境下,只有一个线程可以执行 getInstance() 方法的代码块,从而避免多个线程同时创建实例。
- synchronized 用于确保同一时间只有一个线程可以进入被修饰的方法或代码块。
线程安全的实现
当 线程 A 进入 getInstance() 方法时,其他线程(如 线程 B)会被阻塞,等待 线程 A 完成实例化。线程 B 在进入 getInstance() 方法时,instance 已经被 线程 A 创建,因此不会再次创建实例。
package net.feixiang.creational.singleton.lazy;
/**
* 懒汉式单例类(线程安全)
* 特点:延迟初始化(在第一次使用时创建实例)
*/
public class LazySingleton2 {
// 静态变量用于保存唯一实例(未直接初始化)
private static LazySingleton2 instance;
/**
* 私有构造函数
* 作用:阻止外部通过 new 关键字直接创建实例
*/
private LazySingleton2() {}
/**
* 获取单例实例的全局访问点(线程安全版本)
* @return 单例实例
*/
public static synchronized LazySingleton2 getInstance() {
// 使用时才检查实例是否已创建,未创建则创建实例再返回
if (instance == null) {
instance = new LazySingleton2();
}
return instance;
}
/**
* 示例业务方法
*/
public void doSomething() {
System.out.println("LazySingleton2:Hello FEIXIANG!");
}
}
# 静态内部类实现
1.线程安全
静态内部类的加载是由JVM控制的,JVM会确保在类初始化时只有一个线程能够执行初始化操作,因此这种实现方式是线程安全的。
2.懒加载
静态内部类 SingletonHolder 只有在第一次调用 getInstance() 方法时才会被加载,从而初始化 Singleton 实例。这实现了懒加载,避免了在类加载时就初始化实例。
3.性能优化
不需要使用 synchronized 关键字来保证线程安全,因此在实例创建后,多次调用 getInstance() 方法时不会有同步开销。
4.简洁性
实现相对简洁,避免了复杂的同步逻辑。
总结
静态内部类实现单例模式是一种优雅的实现方式,它结合了饿汉式和懒汉式的优势,既保证了线程安全,又实现了懒加载,同时避免了同步带来的性能开销。推荐在需要线程安全且希望懒加载的场景中使用这种实现方式。
# 静态内部类的实现
package net.feixiang.creational.singleton.staticClass;
/**
* 静态内部类单例类
* 特点:结合懒加载与线程安全的优雅实现(推荐方案)
*/
public class StaticClassSingleton {
/**
* 私有构造函数
* 作用:阻止外部通过 new 关键字创建实例
*/
private StaticClassSingleton() {}
/**
* 静态内部类持有单例实例
* 特性:内部类在第一次被访问时才会加载(实现延迟初始化)
*/
private static class SingletonHolder {
// JVM 保证类加载过程的线程安全性
// static 确保实例唯一
// final 确保实例不可变
private static final StaticClassSingleton instance = new StaticClassSingleton();
}
/**
* 获取单例实例的全局访问点
* 优势:无需同步锁,兼顾性能与线程安全
* @return 单例实例(延迟初始化)
*/
public static StaticClassSingleton getInstance() {
// 触发内部类加载(此时才会初始化实例)
return SingletonHolder.instance;
}
/**
* 示例业务方法
*/
public void doSomething() {
System.out.println("StaticClassSingleton:Hello FEIXIANG!");
}
}
# 枚举实现
枚举实现单例模式是一种简洁且线程安全的方式,它利用了 Java 枚举类型的特性来确保单例的唯一性和线程安全性。以下是枚举实现单例模式的详细解析:
package net.feixiang.creational.singleton.constant;
/**
* 枚举单例模式实现(推荐方案)
* 特点:最简洁安全的单例实现方式(Effective Java 作者推荐)
*/
public enum EnumSingleton {
// 单例实例
INSTANCE;
/**
* 示例业务方法
*/
public void doSomething() {
System.out.println("EnumSingleton:Hello FEIXIANG!");
}
}
1.线程安全
Java 枚举类型的实例创建是线程安全的,JVM 会确保在类加载时只有一个实例被创建。
枚举的实例化过程是由 JVM 控制的,因此不需要额外的同步措施。
2.防止反序列化
枚举类型在反序列化时不会创建新的实例,因此可以防止通过反序列化破坏单例模式。
3.简洁性
枚举实现单例模式的代码非常简洁,不需要额外的 getInstance() 方法或其他复杂的逻辑。
4.唯一性
枚举类型在 Java 中是 final 的,不能被继承,因此可以确保只有一个实例存在。
package net.feixiang.creational.singleton.constant;
/**
* 枚举单例模式实现(推荐方案)
* 特点:最简洁安全的单例实现方式(Effective Java 作者推荐)
*/
public enum EnumSingleton {
// 单例实例
INSTANCE;
/**
* 示例业务方法
*/
public void doSomething() {
System.out.println("EnumSingleton:Hello FEIXIANG!");
}
}
总结
枚举实现单例模式是一种简洁、线程安全且防止反序列化的单例实现方式。它利用了 Java 枚举类型的特性,确保了单例的唯一性和线程安全性。推荐在需要线程安全且防止反序列化的场景中使用这种实现方式。
← 1.设计模式导图

微信公众号

QQ交流群
如若发现错误,诚心感谢反馈。
愿你倾心相念,愿你学有所成。
愿你朝华相顾,愿你前程似锦。