首页 > 其他分享 >23-7-14学习记录

23-7-14学习记录

时间:2023-07-25 19:15:01浏览次数:34  
标签:Singleton 14 记录 对象 23 实例 单例 序列化 变量

1.volatile的作用
volatile关键字有作用是确保被修饰的变量在多线程环境下的可见性和有序性。
可见性(Visibility):当一个变量被声明为volatile时,它的修改对其他线程是可见的。这意味着当一个线程修改了一个volatile变量的值,其他线程能够立即看到最新的值,而不是使用缓存中的旧值。这解决了多线程之间共享变量的可见性问题。
有序性(Atomicity):volatile关键字还可以保证对该变量的读取和写入操作是按照代码的顺序执行的,即不会出现指令重排。这样可以避免在多线程环境下由于指令重排导致的意外行为。-比如双重检查锁单例的空指针问题
需要注意的是,虽然volatile关键字提供了可见性和有序性的保证,但它并不能保证原子性。对于需要进行复合操作的情况,仍然需要使用其他机制(例如synchronized关键字或java.util.concurrent中的原子类)来确保操作的原子性。
总结起来,volatile关键字主要用于确保被修饰的变量在线程之间的可见性和有序性。它在多线程编程中常用于实现线程安全的标志位、状态标记等场景。

2.指令重排
指令重排(Instruction Reordering)是指在计算机系统中,为了提高程序执行效率,处理器和编译器可能会对指令的执行顺序进行重新排序或优化。
在现代计算机系统中,处理器和编译器都会对指令进行各种优化,以最大程度地提高程序的执行速度和效率。指令重排是其中一种优化技术。
指令重排可能会改变指令的执行顺序,但不能改变程序的语义,即程序执行的结果必须与未重排的情况下一致。指令重排主要有以下两个原因:

  1. 数据相关性:某些指令的执行依赖于前面指令的结果。如果前面指令的结果还没有准备好,但后续指令之间不存在数据相关性,处理器可以通过重排指令的顺序来充分利用计算资源,提高执行效率。
  2. 硬件特性:现代处理器采用了多级缓存、流水线执行等技术来提高性能。这些技术可能会导致指令乱序执行或重排,以充分利用处理器的硬件资源。
    尽管指令重排在大多数情况下不会引起问题,但在多线程环境下可能会导致一些隐患。由于指令重排可能改变程序的执行顺序,如果没有适当的同步机制,可能会导致多线程程序出现错误的结果或异常行为。因此,在编写多线程程序时,必须使用适当的同步机制(如volatile关键字、synchronized关键字或java.util.concurrent中的并发工具)来防止指令重排引发的问题。
    总结来说,指令重排是计算机系统中为了提高程序执行效率而对指令执行顺序进行重新排序或优化的技术。它主要基于数据相关性和硬件特性,并且需要适当的同步机制来确保多线程程序的正确性。

3.序列化与反序列化
序列化(Serialization)是指将对象的状态转换为可以存储或传输的形式的过程。在序列化过程中,对象的数据可以被转换为字节流或其他格式,以便在存储或网络传输中进行持久化或传输。

反序列化(Deserialization)是指将序列化后的数据重新转换为对象的过程。在反序列化过程中,先前序列化的数据被解析,并重新构造成相应的对象,使得原始对象的状态可以被恢复。

序列化和反序列化通常用于以下场景:

  1. 对象持久化:将对象的状态保存到磁盘或数据库中,以便在程序重新启动时恢复对象的状态。

  2. 远程通信:在网络传输中将对象转换为字节流,然后在接收端进行反序列化,以实现对象的传输和远程调用。

在Java中,对象的序列化和反序列化通过实现java.io.Serializable接口来实现。Serializable接口是一个标记接口,不包含任何方法。当一个类实现了Serializable接口时,它的对象可以被序列化和反序列化。

Java提供了一些类和工具来支持对象的序列化和反序列化,例如ObjectOutputStreamObjectInputStream类用于序列化和反序列化对象。通过将对象写入输出流进行序列化,然后从输入流读取并恢复对象进行反序列化。

需要注意的是,不是所有的对象都可以被序列化。某些对象可能包含不可序列化的成员变量或具有特殊的序列化需求。在这种情况下,可以通过自定义序列化和反序列化方法来实现对象的定制序列化和反序列化行为。

4.序列化需满足条件
在Java中,可以被序列化和反序列化的对象必须满足以下条件:

  1. 类实现了java.io.Serializable接口:只有实现了Serializable接口的类的对象才能进行序列化和反序列化。Serializable接口是一个标记接口,不包含任何方法。

  2. 对象的成员变量也是可序列化的:如果一个类的对象包含其他对象作为成员变量,那么这些成员变量也必须满足可序列化的条件,即它们也必须实现Serializable接口。

通常情况下,大多数Java标准库中的类都实现了Serializable接口,可以进行序列化和反序列化。例如,StringArrayListHashMap等常见的类都是可序列化的。

然而,并非所有的对象都可以被序列化和反序列化。以下情况下的对象通常不可被序列化:

  1. 未实现Serializable接口的类:如果一个类没有显式地实现Serializable接口,那么它的对象就不支持序列化和反序列化。

  2. 静态成员变量:静态成员变量不属于对象的状态,因此它们不会被序列化和反序列化。只有实例变量才会被序列化和反序列化。

  3. transient修饰的成员变量:如果一个成员变量被标记为transient,它将被视为临时变量,不会被序列化和反序列化。这在某些情况下用于排除敏感信息或临时计算的结果。

需要注意的是,虽然大多数情况下可以进行自动序列化和反序列化,但某些复杂的对象可能需要自定义序列化和反序列化方法,以满足特定的序列化需求。

5.transient关键字
在Java中,transient是一个关键字,用于修饰成员变量。被transient修饰的成员变量在对象的序列化过程中会被标记为不可序列化,从而在序列化和反序列化过程中被忽略。

当一个对象被序列化时,会将对象的状态(即成员变量的值)转换为字节流进行存储或传输。而transient关键字的作用是指示序列化机制忽略被修饰的成员变量,不进行序列化。

常见的使用场景包括:

  1. 敏感信息的排除:当一个对象中包含敏感信息(例如密码、密钥等)时,可以将这些敏感信息的成员变量标记为transient,以避免它们在序列化过程中被存储或传输。

  2. 计算结果的临时性:如果一个对象的成员变量表示临时计算的结果,不需要进行持久化,可以将其标记为transient,以避免将这些临时结果存储到序列化的数据中。

需要注意的是,transient关键字只对对象的序列化过程起作用,对于对象的其他操作(如对象的创建、方法的调用等)没有影响。反序列化过程中,被标记为transient的成员变量会被设置为默认值,如基本类型为0、引用类型为null。

以下是一个示例:

点击查看代码
public class Person implements Serializable {
    private String name;
    private transient int age;  // 被transient修饰的成员变量

    // 省略构造函数和其他方法

    // Getter和Setter方法
}

在上述示例中,Person类实现了Serializable接口,并且age成员变量被标记为transient。当Person对象被序列化时,age成员变量的值将不会被包含在序列化的数据中。

6.破坏单例模式
6.1序列化/反序列化破坏单例模式
序列化和反序列化过程可能会破坏单例模式的原因在于,当一个被序列化的对象被反序列化时,会创建一个新的对象实例,从而破坏了原本设计为单例的对象。

当一个单例对象被序列化时,其内部状态(成员变量的值)会被保存到序列化的数据中。但是,在进行反序列化时,会根据序列化数据重新创建一个新的对象,而不是使用原有的单例对象。

这是因为序列化和反序列化的机制不会调用单例类的构造函数来创建对象,而是通过字节流重建一个新的对象。这样就导致了单例模式中的私有构造函数无法阻止新的对象的创建。

为了解决这个问题,可以在单例类中增加特殊的方法,通过自定义的逻辑来在反序列化过程中返回原有的单例对象。这可以通过实现readResolve()方法来实现。

以下是一个示例,展示了如何在单例类中使用readResolve()方法来防止序列化和反序列化破坏单例模式:

public class Singleton implements Serializable {
    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {
        // 私有构造函数
    }

    public static Singleton getInstance() {
        return INSTANCE;
    }

    // 防止序列化和反序列化破坏单例模式
    private Object readResolve() {
        return INSTANCE;
    }
}

在上述示例中,readResolve()方法被私有化,并返回单例对象的引用。当进行反序列化时,序列化机制会调用readResolve()方法,并将返回的对象用于替换反序列化得到的新对象。这样就确保了单例模式的实例唯一性。

需要注意的是,为了防止通过反射方式破坏单例模式,可以在构造函数中添加逻辑判断,如果已存在单例实例,则抛出异常或返回已存在的实例。

6.2反射破坏单例模式
反射可以破坏单例模式,这是因为在Java中,单例模式通常通过私有的构造方法和静态方法来保证只有一个实例被创建和访问。然而,通过反射,我们可以绕过访问控制,直接访问并调用类的私有构造方法,从而创建多个实例,违背了单例模式的初衷。

以下是一个简单的示例说明如何通过反射破坏单例模式:

public class Singleton {
    private static Singleton instance;

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

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

现在,我们来使用反射来创建多个实例:

public class Main {
    public static void main(String[] args) {
        try {
            // 使用反射获取Singleton类的构造方法
            Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
            // 设置构造方法可访问
            constructor.setAccessible(true);
            // 通过反射创建第一个实例
            Singleton instance1 = constructor.newInstance();
            // 通过正常的单例模式获取第二个实例
            Singleton instance2 = Singleton.getInstance();

            // 检查两个实例是否相同
            System.out.println(instance1 == instance2); // 输出:false
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在上面的代码中,我们通过反射获取了 Singleton 类的私有构造方法,并设置该构造方法可访问。然后,我们通过反射创建了一个新的实例 instance1,并通过正常的单例模式获取了另一个实例 instance2。最后,我们发现 instance1instance2 是不同的实例,这就破坏了单例模式。

为了防止通过反射破坏单例模式,可以在单例类的构造方法中添加逻辑,使得在已经存在实例时,再次创建实例时抛出异常或者直接返回已有实例,从而阻止多次创建实例。例如:

public class Singleton {
    private static Singleton instance;

    private Singleton() {
        if (instance != null) {
            throw new RuntimeException("Cannot create multiple instances of Singleton.");
        }
        // 私有构造方法
    }

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

通过在构造方法中添加判断,即使通过反射创建实例,也会在第二次创建时抛出异常,从而保证单例模式的有效性。但是请注意,这并不能完全阻止通过反射破坏单例模式,因为在Java中,可以使用其他技巧来绕过这种检查。最好的防止反射攻击的方式是使用枚举类型实现单例模式,因为Java保证枚举类型的单例是安全的,无法通过反射来破坏。

标签:Singleton,14,记录,对象,23,实例,单例,序列化,变量
From: https://www.cnblogs.com/liuzheorc/p/17555023.html

相关文章

  • 记录--为啥面试官总喜欢问computed是咋实现的?
    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助从computed的特性出发computed最耀眼的几个特性是啥?1.依赖追踪import{reactive,computed}from'vue'conststate=reactive({a:1,b:2,c:3,})constsum=computed(()=>{returnstat......
  • 2023年7月25日,File类,IO流
    File类1.概述File,是文件和目录路径的抽象表示File只关注文件本身的信息,而不能操作文件里的内容。如果需要读取或写入文件内容,必须使用IO流来完成。在Java中,java.io.File类用于表示文件或目录的抽象路径名。它提供了一组方法,可以用于创建、访问、重命名、删除文件或目录,以及获取......
  • UE5 FPaths 路径 使用记录
    相关路径节点获取配置文件路径FStringUBlueprintPathsLibrary::EngineConfigDir(){ returnFPaths::EngineConfigDir();}注意ProjectContentDir函数编辑模式下返回全路径,运行模式下返回相对路径GetProjectContentDirectory函数返回全路径......
  • 学习生理基础 | 记忆的四个环节2——保持 | 2023年7月25日
    小虾米原创作品,转载请注明出处:https://www.cnblogs.com/shrimp-can/p/17580595.html我们都想高效学习,但如何实现呢?网络上充斥着各种记忆、学习的技巧,能给予我们很大的帮助。但我始终认为,要做好一件事,须得“顺势而为”。那对于学习,什么是这个“势”呢?我认为便是人学习的生理和心......
  • 23种设计模式介绍
    面向对象23种设计模式设计模式的分类按意图分类接口型模式职责型模式构造型模式操作型模式扩展型模式意图模式接口型模式适配器模式、外观模式、合成模式、桥接模式职责型模式代理模式,享元模式、单例模式、观察者模式、调停者模式、职责链模式构造型模......
  • 线程池问题记录以及处理
    现象每天到业务高峰期就会出现提交线程被拒绝。疑问点什么原因会导致activethreads远远小于poolsize的情况下,提交任务失败关键信息线程池配置ExecutorServiceaService=newThreadPoolExecutor(130,300,60L......
  • 2023长郡集训 动态规划笔记
    动态规划原理何为动态规划?动态规划(\(\text{Dynamicprogramming}\)),简称DP。DP并不是一种算法,与模拟、贪心一样,而是一种解决问题的方式。DP的基本思想为「将给定的问题拆分为一个个规模更小的子问题,直到子问题可以直接解决,返回/保存这个值,再根据方程一步步推出原本问题的答......
  • 重磅来袭 | 2023数字供应链安全大会邀请函(DSS 2023)
    2023数字供应链安全大会(DSS2023)将于8月10日在北京·国家会议中心隆重开幕。本次大会由悬镜安全主办,ISC互联网安全大会组委会、中国软件评测中心(工业和信息化部软件与集成电路促进中心)、中国信息通信研究院云计算与大数据研究所、CCF计算机安全专业委员会联合发起,OpenSCA开源社区、......
  • 关于深度学习、NLP和CV,我们写了一本1400页的全栈手册
    不知不觉写文章已经四年了。最开始是一个人,后来恰了恰饭,就招揽了很多比小夕厉害的小伙伴一起写。不知不觉已经积累了300多篇了。。三年以来,我跟小伙伴们原创的300+篇深度学习、NLP、CV、知识图谱、跨模态等领域的入门资料、子方向综述、2018~2022学术前沿解读、工业界炼丹经验与算......
  • 鸟哥的Linux私房菜学习记录
    第零章引入Linux操作系统的概念和背景,介绍了Linux的起源和发展历程,Linux的开源特性使得众多开发者能够共同参与其开发和改进,从而造就了Linux庞大而强大的生态系统。Linux的基本特点,Linux是一个多用户、多任务、多线程的操作系统,它具有稳定性、安全性和灵活性等突出优势Linux的......