核心思想:
属于创建型设计模式,核心目的是确保一个类在整个程序运行期间只有一个实例,并提供一个全局访问点来获取该实例。
控制共享资源的访问(如数据库链接、配置管理、日志处理器等)
真实世界类比:政府是单例模式的一个很好的示例。 一个国家只有一个官方政府。 不管组成政府的每个人的身份是什么,“某政府” 这一称谓总是鉴别那些掌权者的全局访问节点。
结构
所有单例的实现都包含以下两个相同的步骤:
如果你的代码能够访问单例类,那它就能调用单例类的静态方法。无论何时调用该方法,它总是会返回相同的对象。
使用场景:
1. 需要唯一实例的场景:
配置管理类
日志记录器
数据库连接池
多线程环境中的任务调度器
2. 需要全局共享实例
⭐实现方式:
1. 饿汉式(线程安全,类加载时初始化)
1.1. 静态变量式(常见方式)
// 饿汉式(静态变量)
public class Singleton {
// 1. 私有化构造方法
private Singleton() {}
// 2. 创建一个静态变量,保存实例
private static final Singleton instance = new Singleton();
// 3. 提供一个公共的静态方法获取实例
public static Singleton getInstance() {
return instance;
}
}
复制代码
特点:
1.2. 静态代码块式
// 饿汉式(静态代码块)
public class Singleton {
// 1. 私有化构造方法
private Singleton(){}
// 2. 创建一个静态对象
private static Singleton instance;
// 3. 在静态代码块中创建对象
static {
instance = new Singleton();
}
// 4. 提供获取对象的方法
public static Singleton getInstance(){
return instance;
}
}
复制代码
特点:
2. 懒汉式(线程不安全,延迟加载)
// 懒汉式,线程不安全
public class Singleton {
// 1. 私有化构造方法
private Singleton() {}
// 2. 定义一个静态变量,用于存储唯一实例
private static Singleton instance;
// 3. 定义一个静态方法,用于获取唯一实例
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
复制代码
优点:
缺点:
3. 线程安全的懒汉式
3.1. 同步方法
// 懒汉式,同步式,线程安全
public class Singleton {
// 1. 私有化构造方法
private Singleton() {}
// 2. 定义一个静态变量,用于存储
private static Singleton instance;
// 3. 定义一个静态方法,用于获取唯一实例
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
复制代码
缺点:
3.2. 双重检查锁(推荐)
// 懒汉式,双重检查锁方式
public class Singleton {
// 1. 私有化构造方法
private Singleton() {}
// 2. 定义一个静态变量,用于存储实例,volatile保证可见性与有序性,避免指令重排
private static volatile Singleton instance;
// 3. 定义一个静态方法,用于获取唯一实例
public static Singleton getInstance() {
// 1.第一次判断,如果instance的值为null,则进入同步代码块
if (instance == null) {
// 2.同步代码块,保证线程安全
synchronized (Singleton.class) {
// 3.第二次判断,如果instance的值为null,则创建实例
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
复制代码
优点:
注意:volatile
关键字防止指令重排,确保线程安全。
⭐为什么必须要加 volatile
?
1、防止指令重排
在 Java 中,对象的实例化过程分为三步:
由于指令重排的存在,步骤 2 和步骤 3 可能被调换执行。例如:
线程 A 在执行 instance = new Singleton()
时,可能执行了分配内存和赋值操作,但还未完成初始化。
此时,instance
已经不为 null
,但它指向的对象尚未完全初始化。
如果线程 B 此时调用 getInstance()
,判断 instance != null
为真,但实际访问的是一个未初始化完全的对象,这将导致程序出错。
加上 volatile
后,禁止指令重排序,确保初始化顺序正确。
2、保证变量的可见性
Java 的内存模型中,每个线程有自己的工作内存。一个线程对变量的修改,可能不会立即被其他线程所见。
加上 volatile
后,保证每次对 instance
的读操作都能获取到最新的值。
当线程 A 完成 instance
初始化后,其他线程(如 B 线程)立刻可见,而不会读取到旧值或中间状态。
3.3. ⭐静态内部类(推荐)
// 懒汉式,静态内部类方式
public class Singleton {
// 1.构造函数私有化,外部不能new
private Singleton() {}
// 2.创建静态内部类
private static class SingletonHolder {
// 3.创建静态变量,保存实例
private static final Singleton INSTANCE = new Singleton();
}
// 3.定义一个静态方法,用于获取唯一实例
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
复制代码
原理:
优点:
4. ⭐枚举单例(最安全,推荐)
// 枚举单例
public enum Singleton {
INSTANCE;
}
复制代码
优点:
破坏单例
1. 序列化破坏单例
问题:序列化和反序列化可以通过 ObjectInputStream
创建一个新的实例,而不是返回现有的单例实例。
示例代码:
import java.io.*;
public class Singleton implements Serializable {
private static final long serialVersionUID = 1L;
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
public static void main(String[] args) throws Exception {
Singleton instance1 = Singleton.getInstance();
// 将对象序列化到文件
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton.obj"));
oos.writeObject(instance1);
oos.close();
// 从文件反序列化对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singleton.obj"));
Singleton instance2 = (Singleton) ois.readObject();
// 验证是否为同一个实例
System.out.println(instance1 == instance2); // 输出:false
}
}
复制代码
原因:
解决方案:实现 readResolve()
方法,确保反序列化时返回现有实例。
private Object readResolve() {
return INSTANCE;
}
复制代码
2. 反射破坏单例
问题:通过反射,能够直接调用私有构造方法,创建多个实例。
示例代码:
import java.lang.reflect.Constructor;
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
public static void main(String[] args) throws Exception {
Singleton instance1 = Singleton.getInstance();
// 使用反射创建新实例
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton instance2 = constructor.newInstance();
// 验证是否为同一个实例
System.out.println(instance1 == instance2); // 输出:false
}
}
复制代码
原因:
解决方案:
1、在构造方法中防止重复实例化:
private static boolean isCreated = false;
private Singleton() {
if (isCreated) {
throw new RuntimeException("Singleton instance already created!");
}
isCreated = true;
}
复制代码
2、使用枚举单例:
枚举类的单例天然防止反射和序列化破坏。
public enum Singleton {
INSTANCE;
}
复制代码
3. 总结
在源码中的应用
1. Runtime
类
源码分析:
public class Runtime {
private static final Runtime currentRuntime = new Runtime(); // 饿汉式实例化
private Runtime() {} // 私有化构造方法
public static Runtime getRuntime() {
return currentRuntime; // 返回唯一实例
}
public void gc() {
// 调用垃圾回收
}
public void exit(int status) {
// 退出 JVM
}
}
复制代码
特点:
2. Desktop
类
源码分析:
public final class Desktop {
private static Desktop desktop;
private Desktop() {}
public static synchronized Desktop getDesktop() {
if (desktop == null) {
desktop = new Desktop(); // 懒汉式单例
}
return desktop;
}
public void browse(URI uri) {
// 打开 URI
}
}
复制代码
特点:
使用同步方法保证线程安全。
懒加载,实例在需要时创建。
3. Logger
类( java.util.logging.Logger
)
源码分析(核心部分):
public class Logger {
private static final LogManager manager = LogManager.getLogManager(); // 单例的 LogManager
protected Logger(String name, String resourceBundleName) {
// Logger 构造方法
}
public static Logger getLogger(String name) {
return manager.getLogger(name); // 通过单例 LogManager 获取 Logger
}
}
复制代码
特点:
4. 总结
在 JDK 源码中,单例模式被广泛应用于需要 全局唯一实例 或 资源共享 的场景:
饿汉式:Runtime
类。
懒汉式:Desktop
类。
组合模式:Logger
类中的 LogManager
单例。
这些设计的核心目标是:确保全局状态的一致性、节省资源以及简化管理。
单例模式优缺点:
与其他模式的关系:
外观模式类通常可以转换为单例模式类, 因为在大部分情况下一个外观对象就足够了。
如果你能将对象的所有共享状态简化为一个享元对象, 那么享元模式就和单例类似了。 但这两个模式有两个根本性的不同。
只会有一个单例实体, 但是享元类可以有多个实体, 各实体的内在状态也可以不同。
单例对象可以是可变的。 享元对象是不可变的。
抽象工厂模式、 生成器模式和原型模式都可以用单例来实现。
文章转载自:SlackClimb
原文链接:https://www.cnblogs.com/cikiss/p/18684045
体验地址:http://www.jnpfsoft.com/?from=001YH
评论