在序列化、反序列化下如何保持单例(Singleton)模式

1、序列化、反序列化

在 Java 中,当一个对象被序列化后再被反序列化,通常情况下会创建一个新的对象实例。这是因为序列化将对象的状态保存到字节流中,而反序列化则是将字节流重新转化为对象。在这个过程中,通常会使用类的构造函数创建一个新的对象,并将保存的状态设置给这个新对象。

这意味着,默认情况下,在序列化和反序列化过程中,会产生新的对象实例,而不是保持原有的对象实例。这可能会导致一些问题,特别是在设计为单例(Singleton)的类或者一些需要保持引用相等性的场景下。

2、DoubleChecked单例

cpp 复制代码
public class DoubleCheckedSingleton implements Serializable{
    private volatile static DoubleCheckedSingleton instance;
    private static final long serialVersionUID = 1L;

    private DoubleCheckedSingleton() {
        // 私有构造方法
    }

    public static DoubleCheckedSingleton getInstance() {
        if (instance == null) {
            synchronized (DoubleCheckedSingleton.class) {
                if (instance == null) {
                    instance = new DoubleCheckedSingleton();
                }
            }
        }
        return instance;
    }
}

示例中,通过使用 volatile 关键字修饰 instance,确保了线程间的可见性。在 getInstance() 方法中,首先检查 instance 是否为 null,如果为 null,才会进入同步代码块。在同步代码块内,再次检查 instance 是否为 null,这是为了防止其他线程已经在等待同步锁的情况下创建了实例。如果没有其他线程已经创建了实例,就在同步代码块内创建实例。这种方式可以减少同步的次数,提高性能。

需要注意的是,虽然现代的Java版本中双重检查锁定通常是线程安全的,但在某些特殊情况下仍可能出现问题,如序列化、反射等情况。

解决上述代码的这个问题,可以在类中实现 readResolve() 方法,确保在反序列化时返回同一个对象实例,从而维护对象的单例特性。

cpp 复制代码
public class DoubleCheckedSingleton implements Serializable{
    private volatile static DoubleCheckedSingleton instance;
    private static final long serialVersionUID = 1L;

    private DoubleCheckedSingleton() {
        // 私有构造方法
    }

    public static DoubleCheckedSingleton getInstance() {
        if (instance == null) {
            synchronized (DoubleCheckedSingleton.class) {
                if (instance == null) {
                    instance = new DoubleCheckedSingleton();
                }
            }
        }
        return instance;
    }
// 重写 readResolve 方法,确保反序列化时返回同一个单例对象
    protected Object readResolve() {
        return instance;
    }
}

这个示例中,我添加了实现 Serializable 接口的代码,并重写了 readResolve() 方法,以确保在反序列化时返回同一个单例对象。这样,即使在序列化和反序列化过程中,也能保持单例的一致性。

3、静态内部类和枚举单例

如果你更关注简单性和可靠性,也可以考虑使用静态内部类或枚举单例来实现线程安全的单例模式。

因为这两种方法都充分利用了Java语言的特性来保证线程安全性,同时也能有效地处理序列化、反射等问题

静态内部类单例

cpp 复制代码
public class StaticInnerClassSingleton {

    private StaticInnerClassSingleton() {
        // 私有构造方法
    }

    private static class SingletonHolder {
        private static final StaticInnerClassSingleton instance = new StaticInnerClassSingleton();
    }

    public static StaticInnerClassSingleton getInstance() {
        return SingletonHolder.instance;
    }
}

序列化

cpp 复制代码
import java.io.Serializable;

public class StaticInnerClassSingleton implements Serializable {

    private StaticInnerClassSingleton() {
        // 私有构造方法
    }

    private static class SingletonHolder {
        private static final StaticInnerClassSingleton instance = new StaticInnerClassSingleton();
    }

    public static StaticInnerClassSingleton getInstance() {
        return SingletonHolder.instance;
    }

    // 添加此方法以支持序列化
    protected Object readResolve() {
        return getInstance();
    }
}

这个实现中,静态内部类 SingletonHolder 仅在需要时才会被加载,确保了懒加载的特性。

同时,由于类加载器的机制,这种方式可以保证线程安全。

静态内部类只会被加载一次,因此在多线程环境中也能够确保单例实例的唯一性。

枚举单例

cpp 复制代码
public enum EnumSingleton {
    INSTANCE;

    // 添加需要的方法和属性

    public void doSomething() {
        // 实现方法
    }
}

序列化

cpp 复制代码
import java.io.Serializable;

public enum EnumSingleton implements Serializable {
    INSTANCE;

    // 添加需要的方法和属性

    public void doSomething() {
        // 实现方法
    }
}

使用枚举单例可以保证在任何情况下都只有一个实例被创建,包括在多线程环境下以及在序列化、反射等特殊情况下。

枚举类的实例创建是线程安全的,而且枚举类不会被反射破坏,并且可以处理序列化和反序列化,保证了单例模式的可靠性。

相关推荐
码兄科技4 小时前
Java AI智能体开发实战:从零构建智能对话系统指南
java·开发语言·人工智能
折哥的程序人生 · 物流技术专研4 小时前
第8篇:六大设计原则在工厂模式中的体现
java·设计模式·设计原则·工厂模式·编程进阶·扩充系列
小明bishe185 小时前
计算机毕业设计之基于JAVA的植物科普网站
java·spring boot·spring·架构·课程设计
Drone_xjw5 小时前
从 GDB 到 CDB:C/C++ 程序调试的两把“手术刀”
c语言·开发语言·c++
r_oo_ki_e_5 小时前
Java Map 集合学习笔记
java·笔记·学习
SL-staff5 小时前
智慧园区2000+设备统一管理:JVS-IoT如何降低运维成本40%
java·开发语言·物联网·智慧园区·设备管理·运维优化·jvs-iot
咖啡八杯6 小时前
GoF设计模式——模板方法模式
java·后端·spring·设计模式
爱笑的源码基地6 小时前
一款全开源的信创云PACS影像云平台解决方案,支持多模态医学影像处理(CT/MR/DR/超声/病理等)
java·源码·国产化·pacs·云影像·区域pacs
我命由我123457 小时前
Android 开发问题:ClickableSpan 的点击事件没有生效
java·java-ee·android studio·android jetpack·android-studio·android runtime
2zcode7 小时前
基于MATLAB图像处理的饮料瓶灌装液位检测系统设计与实现
开发语言·图像处理·matlab