BGW总结

Java基础

Java概述

面向对象和面向过程的区别

面向过程性能比面向对象高,因为类调用时需要实例化,开销比较大

面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护

三大特性

①封装:指属性私有化,别的类或者方法要访问属性必须通过getter和setter方法,这样可以隐藏具体属性和实现细节,增强安全性

②继承:指将多个相同属性和方法提取出来作为一个父类,一个类只能继承一个父类,且只能继承非private的属性和方法,可以提高代码的复用性

③多态:所谓多态就是指程序中定义的引用变量引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定

JVM、JDK 和 JRE简介

JVM是运行Java字节码的虚拟机,针对不同的平台,Java有不同的虚拟机,识别同样的字节码调用不同平台上的操作系统API来完成操作,从而实现"一次编译,到处执行"。

JRE 是 Java 运行时环境,包括JVM 的标准实现和 Java 的一些基本类库。它相对于 JVM 来说,多出来的是一部分的 Java 类库。

JDK是Java 开发工具包。JDK 是整个 Java 开发的核心,它集成了 JRE 和一些好用的小工具。例如:javac.exe,java.exe,jar.exe 等。

Java语言有哪些特点

简单易学(Java语言的语法与C语言和C++语言很接近)

面向对象(封装,继承,多态)

平台无关性(Java虚拟机实现平台无关性)

支持网络编程并且很方便(Java语言诞生本身就是为简化网络编程设计的)

支持多线程(多线程机制使应用程序在同一时间并行执行多项任)

健壮性(Java语言的强类型机制、异常处理、垃圾的自动收集等)

安全性

什么是字节码?采用字节码的大好处是什么

字节码:Java源代码经过虚拟机编译器编译后产生的文件(即扩展为.class的文 件),它不面向任何特定的处理器,只面向虚拟机。

Java中的编译器和解释器工作流程:Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟机 器。这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口。编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代 码转换为特定系统的机器码执行。在Java中,这种供虚拟机理解的代码叫做字节 码(即扩展为.class的文件),它不面向任何特定的处理器,只面向虚拟机。每 一种平台的解释器是不同的,但是实现的虚拟机是相同的。Java源程序经过编译 器编译后变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节 码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定的机器上运 行,这就是上面提到的Java的特点的编译与解释并存的解释。

采用字节码的好处:Java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的 问题,同时又保留了解释型语言可移植的特点。所以Java程序运行时比较高效, 而且,由于字节码并不专对一种特定的机器,因此,Java程序无须重新编译便可 在多种不同的计算机上运行。

什么是Java程序的主类?应用程序和小程序的主类有何不同?

一个程序中可以有多个类,但只能有一个类是主类。在Java应用程序中,这个主 类是指包含main()方法的类。而在Java小程序中,这个主类是一个继承自系统 类JApplet或Applet的子类。应用程序的主类不一定要求是public类,但小程序 的主类要求必须是public类。主类是Java程序执行的入口点。

Java应用程序与小程序之间有那些差别?

简单说应用程序是从主线程启动(也就是main()方法)。applet小程序没有main 方法,主要是嵌在浏览器页面上运行(调用init()线程或者run()来启动)。

Java和C++的区别
  • 都是面向对象的语言,都支持封装、继承和多态
  • Java不提供指针来直接访问内存,程序内存更加安全,而Java有自动内存管理机制,不需要程序员手动释放无用内存
  • Java的类是单继承的,C++支持多重继承;虽然Java的类不可以多继承,但是接口可以多继承。
Oracle JDK 和 OpenJDK 的对比
  • Oracle JDK版本将每三年发布一次,而OpenJDK版本每三个月发布一次
  • OpenJDK 是一个参考模型并且是完全开源的,而Oracle JDK是OpenJDK 的一个实现,并不是完全开源的
  • Oracle JDK比OpenJDK更稳定。OpenJDK和Oracle JDK的代码几乎相同,但Oracle JDK有更多的类和一些错误修复
  • 在响应性和 JVM 性能方面,Oracle JDK 与 OpenJDK 相比提供了更好的性能
什么是字节码?采用字节码的最大好处是什么

字节码
Java源代码经过虚拟机编译后生成的文件(即.class文件),他不面向特定的处理器,只面向虚拟机
采用字节码的好处
在保留解释型语言可移植性特点的前提下,在一定程度上解决了解释型语言执行效率低的问题

什么是Java程序的主类
  • 一个程序中可以有多个类,但只能有一个类是主类,是Java程序执行的入口点
  • 在应用程序中,主类指包含main()方法的类,而在Java小程序中,这个主类是一个继承自系统类JApplet或Applet的子类
  • 应用程序的主类不一定要求是public类,但小程序 的主类要求必须是public类
Java和C++的区别
  • 都是面向对象的语言,都支持封装、继承和多态
  • Java有内存管理机制,不需要程序员手动释放内存,而C++通过指针管理内存
  • Java的类是单继承的,但可以继承多个接口,C++可以多继承

基础语法

Java有哪些数据类型

定义:Java语言是强类型语言,对于每一种数据都定义了明确的具体的数据类 型,在内存中分配了不同大小的内存空间。

分类
基本数据类型有byte、short、int、long、float、double、char、boolean
引用数据类型有类、接口、数组

Java基本数据类型图
在这里插入图片描述

switch 是否能作用在 byte 上,是否能作用在 long 上,是否能作用在 String 上

在 Java 5 以前,switch(expr)中,expr 只能是 byte、short、char、int。从 Java5 开始,Java 中引入了枚举类型,expr 也可以是 enum 类型,从 Java 7 开始,expr 还可以是字符串(String),但是长整型(long)在目前所有的版本中都是不可以的。
char, byte, short, int, Character, Byte, Short, Integer, String, or an enum

用最有效率的方法计算 2 乘以 8

2 << 3(左移 3 位相当于乘以 2 的 3 次方,右移 3 位相当于除以 2 的 3 次 方)。

short s1 = 1; s1 = s1 + 1;有错吗?short s1 = 1; s1 += 1;有错吗

对于 short s1 = 1; s1 = s1 + 1;由于 1 是 int 类型,因此 s1+1 运算结果也是 int型,需要强制转换类型才能赋值给 short 型。 而 short s1 = 1; s1 += 1;可以正确编译,因为 s1+= 1;相当于 s1 = (short(s1 + 1);其中有隐含的强制类型转换。

Java语言采用何种编码方案?有何特点?

Java语言采用Unicode编码标准,Unicode(标准码),它为每个字符制订了一个唯一的数值,因此在任何的语言,平台,程序都可以放心的使用。

访问修饰符 public,private,protected,以及不写(默认)时的区别

定义:Java中,可以使用访问修饰符来保护对类、变量、方法和构造方法的访 问。Java 支持 4 种不同的访问权限。

在这里插入图片描述

Java 中的final
  • 被final修饰的类不可以被继承
  • 被final修饰的方法不可以被重写
  • 被final修饰的变量不可以被改变,被final修饰不可变的是变量的引用,而不是引用指向的内容,引
    用指向的内容是可以改变的
final finally finalize区别
  • final可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表示该变量是一个常量不能被重新赋值。
  • finally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法finally代码块中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。
  • finalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,该方法一般由垃圾回收器回收垃圾回收垃圾时调用,是一个对象是否可回收的最后判断。
this与super的区别
  • super指向当前对象的直接父类,this代表当前对象
  • this和super不能同时出现在一个构造函数里面
  • super()和this()均需放在构造方法内第一行
  • this()和super()都指的是对象,所以,均不可以在static环境中使用
static

static应用场景

  • 修饰成员变量,方法
  • 静态代码块
  • 修饰类【只能修饰内部类也就是静态内部类】

主要意义
static的主要意义是在于创建独立于具体对象的域变量或者方法。以致于即使没有创建对象,也能使用属
性和调用方法!

static关键字还有一个比较关键的作用就是 用来形成静态代码块以优化程序性能。static块可以置于类中
的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static
块,并且只会执行一次。为什么说static块可以用来优化程序性能,是因为它的特性:只会在类加载的时
候执行一次。因此,很多时候会将一些只需要进行一次的初始化操作都放在static代码块中进行。

抽象类和接口的对比

抽象类是用来捕捉子类的通用特性的。接口是抽象方法的集合。
从设计层面来说,抽象类是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。

相同点

  • 接口和抽象类都不能实例化,用于被其他实现或继承
  • 都可以包含抽象方法,其子类都必须覆写这些抽象方法

不同点
在这里插入图片描述
备注
Java8中接口中引入默认方法和静态方法,现在,我们可以为接口提供默认实现的方法了,并且不用强制子类来实现它。 接口和抽象类各有优缺点,在接口和抽象类的选择上,必须遵守这样一个原则:

  • 行为模型应该总是通过接口而不是抽象类定义,所以通常是优先选用接口,尽量 少用抽象类。
  • 选择抽象类的时候通常是如下情况:需要定义子类的行为,又要为子类提供通用 的功能。
普通类和抽象类有哪些区别?
  • 普通类不能包含抽象方法,抽象类可以包含抽象方法。
  • 抽象类不能直接实例化,普通类可以直接实例化。
构造器(constructor)是否可被重写(override)

构造器不能被继承,因此不能被重写,但可以被重载。

重载(Overload)和重写(Override)的区别。重载的方法能否根据返回类型进行区分?

重载:发生在同一个类中,方法名相同参数列表不同(参数类型不同、个数不 同、顺序不同),与方法
返回值和访问修饰符无关,即重载的方法不能根据返回类型进行区分。

重写:发生在父子类中,方法名、参数列表必须相同,返回值小于等于父类,抛出的异常小于等于父
类,访问修饰符大于等于父类(里氏代换原则);如果父类方法访问修饰符为private则子类中就不是重
写。

在Java中定义一个不做事且没有参数的构造方法的作用

Java程序在执行子类的构造方法之前,如果没有用super()来调用父类特定的构 造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中只定义了 有参数的构造方法,而在子类的构造方法中又没有用super()来调用父类中特定 的构造方法,则编译时将发生错误,因为Java程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。

字符型常量和字符串常量的区别
  • 形式上: 字符常量是单引号引起的一个字符字符串常量是双引号引起的
    若干个字符
  • 含义上: 字符常量相当于一个整形值( ASCII 值),可以参加表达式运算 字
    符串常量代表一个地址值(该字符串在内存中存放位置)
  • 占内存大小 字符常量只占 2 个字节 字符串常量占若干个字节(至少一个字符结束标志) (注意: char 在 Java 中占两个字节)
在 Java 中定义一个不做事且没有参数的构造方法的作用

Java 程序在执行子类的构造方法之前,如果没有用 super() 来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用 super() 来调用父类中特定的构造方法,则编译时将发生错误,因为 Java 程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。

== 和 equals 的区别是什么

== : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同 一个对象。(基本数据类型
== 比较的是值,引用数据类型 == 比较的是内存地址)

equals() : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:

  • 类没有覆盖equals() 方法。则通过 equals() 比较该类的两个对象时, 等价于通过“==”比较这两个对象。
  • 类覆盖了 equals() 方法。一般,我们都覆盖 equals() 方法来两个对象 的内容相等;若它们的内
    容相等,则返回 true (即,认为这两个对象相等)。
hashCode 与 equals

hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是
确定该对象在哈希表中的索引位置。hashCode() 定义 在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode()函数。

应用场景
当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对 象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对 象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的Java启蒙书《Head first java》 第二版)。这样我们就大大减少了equals 的次数,相应就大大提高了执行速度。如果两个对象相等,则hashcode一定也是相同的,因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖。

Java的方法传参是值传递还是引用传递

是值传递。Java 语言的方法调用只支持参数的值传递。当一个对象实例作为一 个参数被传递到方法中时,参数的值就是对该对象的引用。对象的属性可以在被 调用过程中被改变,但对对象引用的改变是不会影响到调用者的。

值传递和引用传递有什么区别

值传递:指的是在方法调用时,传递的参数是按值的拷贝传递,传递的是值的拷 贝,也就是说传递后就互不相关了。 引用传递:指的是在方法调用时,传递的参数是按引用进行传递,其实传递的引 用的地址,也就是变量所对应的内存空间的地址。传递的是值的引用,也就是说 传递前和传递后都指向同一个引用(也就是同一个内存空间)。

JDK 中常用的包有哪些
  • java.lang:这个是系统的基础类;
  • java.io:这里面是所有输入输出有关的类,比如文件操作等;
  • java.nio:为了完善 io 包中的功能,提高 io 包中性能而写的一个新包;
  • java.net:这里面是与网络有关的类;
  • java.util:这个是系统辅助类,特别是集合类;
  • java.sql:这个是数据库操作的类。
java和javax有什么区别

刚开始的时候 JavaAPI 所必需的包是 java 开头的包,javax 当时只是扩展 API 包来说使用。然而随着时间的推移,javax 逐渐的扩展成为 Java API 的组成部 分。但是,将扩展从 javax 包移动到 java 包将是太麻烦了,最终会破坏一堆现 有的代码。因此,最终决定 javax 包将成为标准API的一部分。 所以,实际上java和javax没有区别。这都是一个名字。

什么是内部类?

在Java中,可以将一个类的定义放在另外一个类的定义内部,这就是内部类。内部类本身就是类的一个
属性,与其他属性定义方式一致。

内部类的分类有哪些

内部类可以分为四种:成员内部类、局部内部类、匿名内部类和静态内部类。
静态内部类
定义在类内部的静态类,就是静态内部类。

成员内部类
定义在类内部,成员位置上的非静态类,就是成员内部类。

局部内部类
定义在方法中的内部类,就是局部内部类。

匿名内部类
匿名内部类就是没有名字的内部类,有以下特点:

  • 匿名内部类必须继承一个抽象类或者实现一个接口。
  • 匿名内部类不能定义任何静态成员和静态方法。
  • 当所在的方法的形参需要被匿名内部类使用时,必须声明为 final。
  • 匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。
内部类的优点
  • 一个内部类对象可以访问创建它的外部类对象的内容,包括私有数据!
  • 内部类不为同一包的其他类所见,具有很好的封装性;
  • 内部类有效实现了“多重继承”,优化 java 单继承的缺陷。
  • 匿名内部类可以很方便的定义回调。
String StringBuffer 和 StringBuilder 的区别(可变性、安全性、性能)
  • String 类中使用 private final char value[]保存字符串,对象是不可变的,所以线程安全,但是每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。
  • StringBuffer 和 StringBuilder使用 private char value[]保存字符串,所以这两种对象都是可变的,但是StringBuffer 对方法加了同步锁,所以是线程安全的,StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的,但是性能比较高
toString() 和 强制类型转换 (String)、String.valueOf()的区别
  • toString()返回以文本形式显示的字符串,Object超类中的源码是getClass().getName() + ‘@’ + Integer.toHexString(hashCode())
  • String.valueOf()相比toString(),调用的对象不可以直接为null,但调用的对象可以指向null,源码为return (obj == null) ? “null” : obj.toString();
  • (String)可以将String的父类都强制类型转换为String类型,基本数据类型和null也可以被强制类型转换
    注意:使用String.valueOf()时要防止null转换后变成字符串”null”,导致校验字符串为空出错或者null
int 和 Integer 有什么区别
  • int 是基本数据类型,Integer是其包装类,注意是一个类
  • Integer可以用于各种数据类型之间的转化,同时,只有包装类能作为泛型参数,而基本数据类型不可以
自动装箱与拆箱

装箱:将基本类型用它们对应的引用类型包装起来;
拆箱:将包装类型转换为基本数据类型;

IO流

java 中 IO 流分为几种?

按照流的流向分,可以分为输入流和输出流;
按照操作单元划分,可以划分为字节流和字符流;
按照流的角色划分为节点流和处理流。
Java Io流共涉及40多个类,这些类看上去很杂乱,但实际上很有规则,而且彼 此之间存在非常紧密的联系, Java I0流的40多个类都是从如下4个抽象类基类中派生出来的。

  • InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
  • OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
    在这里插入图片描述
    在这里插入图片描述
BIO,NIO,AIO 有什么区别?
  • BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。
  • NIO:Non IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过Channel(通道)通讯,实现了多路复用。
  • AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO,异步 IO 的操作基于事件和回调机制。

集合

概述

常用集合

Collection 对象的集合(单列集合)
├——List 接口:保持插入顺序,可重复,可存多个null
│————— LinkedList 链表, 插入删除, 没有同步, 线程不安全
│————— ArrayList 数组, 随机访问, 没有同步, 线程不安全
│————— Vector 数组, 同步, 线程安全
└——Set 接口: 不可重复,可存多个null
├—————HashSet 使用hash表(数组 + 链表)存储元素
│———————— LinkedHashSet 链表维护元素的插入次序
└—————TreeSet 底层实现为二叉树,元素排好序,因此需要定义比较器

Map 接口:键值对的集合
├———Hashtable 同步,线程安全
├———HashMap 没有同步,线程不安全
└———TreeMap 红黑树对所有的key进行排序

集合与数组的区别
  • 长度:数组固定,集合可变
  • 内容:数组可以是基本类型,也可以是引用类型,而集合只能是引用类型
List和Set的区别

①重复对象:list方法可以允许重复的对象,而set方法不允许重复对象

②null元素:list可以插入多个null元素,而set只允许插入一个null元素

③是否有序:list是一个有序的容器,保持了每个元素的插入顺序。即输出顺序就是输入顺序,而set方法是无序容器,无法保证每个元素的存储顺序,TreeSet通过 Comparator 或者 Comparable 维护了一个排序顺序

List和Map区别

List是对象集合,允许对象重复。 Map是键值对的集合,不允许key重复。

List

Arraylist 与 LinkedList 区别

Arraylist
优点:ArrayList是实现了基于动态数组的数据结构,因为地址连续,一旦数据存储好了,查询操作效率会比较高(在内存里是连着放的)。
缺点:因为地址连续, ArrayList要移动数据,所以插入和删除操作效率比较低。
LinkedList
优点:LinkedList基于链表的数据结构,地址是任意的,所以在开辟内存空间的时候不需要等 一个连续的地址,对于新增和删除操作add和remove,LinedList比较占优势。LinkedList 适 用于要头尾操作或插入指定位置的场景
缺点:因为LinkedList要移动指针,所以查询操作性能比较低。
适用场景分析
当需要对数据进行对此访问的情况下选用ArrayList,当需要对数据进行多次增加删除修改时 采用LinkedList。

ArrayList 与 Vector 区别
  • Vector是线程安全的,而ArrayList不是线程安全的,因此Vector效率低
  • 两者都是用数组实现的,但Vector可以设置增长因子,而ArrayList每次增长都是原来的1.5倍
如何实现数组和 List 之间的转换?
  • 数组转 List:使用 Arrays.asList(array) 进行转换。
  • List 转数组:使用 List 自带的 toArray() 方法。
多线程场景下如何使用 ArrayList?

ArrayList 不是线程安全的,如果遇到多线程场景,可以通过 Collections 的synchronizedList 方法将其转换成线程安全的容器后再使用。

Map

HashMap 和 Hashtable 的区别
  • hashTable同步的,而HashMap是非同步的,效率上比hashTable要高
  • hashMap允许空键值,而hashTable不允许
HashMap 和 ConcurrentHashMap 的区别
  • ConcurrentHashMap对整个桶数组进行了分割分段(Segment),然后在每一个分段上都用lock锁进行保护,相对于HashTable的synchronized关键字锁的粒度更精细了一些,并发性能更好,而HashMap没有锁机制,不是线程安全的
  • HashMap的键值对允许有null,但是ConCurrentHashMap都不允许
ConcurrentHashMap 的工作原理

HashTable里使用的是synchronized关键字,这其实是对对象加锁,锁住的都是对象整体, 当Hashtable的大小增加到一定的时候,性能会急剧下降,因为迭代时需要被锁定很长的时间。ConcurrentHashMap引入了分割(Segment),把一个大的Map拆分成N个小的HashTable,在put方法中,会根据hash(paramK.hashCode())来决定具体存放进哪个Segment,如果查看Segment的put操作,我们会发现内部使用的同步 机制是基于lock操作的,这样就可以对Map的一部分(Segment)进行上锁,这样影响的只是将要放入同一个Segment的元素的put操作,保证同步的时候,锁住的不是整个 Map(HashTable就是这么做的),相对于HashTable提高了多线程环境下的性能,因此 HashTable已经被淘汰了。

能否使用任何类作为 Map 的 key?

可以使用任何类作为 Map 的 key,然而在使用之前,如果类重写了 equals() 方法,也应该重写 hashCode() 方法。

为什么HashMap中String、Integer这样的包装类适合作为Key?
  • 内部已重写了equals()、hashCode()等方法,遵守了HashMap内部的规范
  • 都是final类型,即不可变性,保证key的不可更改性,不会存在获取hash值不同的情况
HashMap是怎么解决哈希冲突的?

Hash,一般翻译为“散列”,就是把任意长度的输入通过散列算法,变换成固定长度的输出,该输出就是散列值(哈希值);这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来唯一的确定输入值,所以才会有哈希冲突(当两个不同的输入值,根据同一散列函数计算出相同的散列值的现象,我们就把它叫做碰撞(哈希碰撞))。

HashMap使用数组+链表+红黑树的数据结构来存储数据,当我们插入数据的时候,先用hash算法算出哈希值,然后再通过HashMap中的hash方法算出数组下标,如果该下标下无值,则直接存储数据到链表中,如果有数据,则遍历链表,如果有存在hashCode和key相等的对象,则覆盖,否则采用尾插法插入到链表(1.7使用头插法),这样就解决了hash冲突存值问题。


说一下 HashMap 的实现原理?
HashMap 的长度为什么是2的幂次方
HashMap的hash函数是怎样的
HashMap的数据结构是怎样的
HashMap的put方法执行流程是怎样的
ConcurrentHashMap 的实现原理

其他

Iterator、iterator()和Iterable的关系

Collection继承Iterable接口,因此继承Collection的集合类都需要实现Iterable接口中的iterator()方法,而Iterator是iterator()方法中返回的对象类型
在这里插入图片描述

Iterator与ListIterator

①相同点:都是迭代器,当需要对集合中元素进行遍历不需要干涉其遍历过程时,这两种迭代器都可以使用

②不同点:

  • 使用范围不同,Iterator可以应用于所有的集合,Set、List和Map和这些集合的子类型。而ListIterator只能用于List及其子类型
  • ListIterator有add方法,可以向List中添加/修改对象,而Iterator不能
  • ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。Iterator不可以
  • ListIterator可以定位当前索引的位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能

在这里插入图片描述

如何边遍历边移除 Collection 中的元素?

边遍历边修改 Collection 的唯一正确方式是使用Iterator.remove() 方法,使用list.remove()时会报错,这是因为当使用foreach(for(Integer i : list)) 语句时,会自动生成一个iterator 来遍历该 list,但同时该list 正在被 Iterator.remove() 修改。Java 一般不允许一个线程在遍历 Collection 时另一个线程修改它。

怎么确保一个集合不能被修改?

可以使用 Collections.unmodifiableCollection(Collection c) 方法来创建一个只读集合,这样改变集合的任何操作都会抛出 Java.lang.UnsupportedOperationException 异常。

简述一下fail-safe 机制与fail-fast 机制

fail-safe 和 fail-fast,是多线程并发操作集合时的一种失败处理机制。

Fail-fast:表示快速失败,在集合遍历过程中,一旦发现容器中的数据被修改了,会立刻抛出ConcurrentModificationException 异常,从而导致遍历失败。java.util 包下的集合类都是快速失败机制的,常见的的使用fail-fast 方式遍历的容器有 HashMap 和 ArrayList 等。

Fail-safe,表示失败安全,也就是在这种机制下,出现集合元素的修改,不会抛出ConcurrentModificationException。原因是采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。java.util.concurrent 包下的容器都是安全失败的,可以在多线程下并发使用,并发修改。

反射

什么是反射机制?

Java反射机制是在运行状态中构造任意一个类的对象;能够知道任意一个类的所有属性和方法;调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。

反射机制优缺点

优点: 运行期类型的判断,动态加载类,提高代码灵活度。
缺点: 反射性能比直接的java代码要慢很多。

Java获取反射的三种方法
  • 通过new对象实现反射机制
  • 通过路径实现反射机制
  • 通过类名实现反射机制
反射机制的应用场景有哪些?
  • 我们在使用JDBC连接数据库时使用Class.forName()通过反射加载数据库的驱动程序;
  • Spring框架也用到很多反射机制, 经典的就是xml的配置模式
  • 使用反射机制,根据这个字符串获得某个类的Class实例
  • 动态代理设计模式也采用了反射机制
JDK动态代理

JDK动态代理就是使用Proxy类的newProxyInstance方法,根据实现InvocationHandler接口的类生成一个拥有被代理接口的所有方法的动态代理类,在动态代理类中可以设置被代理类方法执行前后需要执行的操作,例子如下:

public class JDKDynamicProxyTest {

    /**
     * 被代理接口
     */
    interface Subject {
        void action();
    }

    /**
     * 被代理类
     */
    static class SubjectImpl implements Subject {

        @Override
        public void action(){
            System.out.println("excute action!");
        };

    }

    /**
     * JDK动态代理类
     */
    static class DynamicProxyHandler implements InvocationHandler {

        private Object target;

        public DynamicProxyHandler(Object target){
            this.target = target;
        }

		/**
         * 使用Proxy类生成动态代理类
         * @param <T>
         * @return
         */
        public <T> T getProxy(){
            return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
        }

        public void before() {
            System.out.println("do something before!");
        }

        public void after() {
            System.out.println("do something after!");
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            before();
            method.invoke(target, args);
            after();
            return null;
        }
    }

    public static void main(String[] args){
        Subject proxy = new DynamicProxyHandler(new SubjectImpl()).getProxy();
        proxy.action();
    }

}


执行结果:
do something before!
excute action!
do something after!
CGLIB动态代理

CGLIB(Code Generation Library)是一个开源项目!是一个强大的,高性能,高质量的Code生成类库,
它可以在运行期扩展Java类与实现Java接口。使用它可以动态生成被代理类的子类,子类重写非final修饰的方法,实现在执行方法前后进行自定义操作,实现动态代理。

public class CGLIBDynamicProxyTest {

    public static class Target {
        public Target(){
            System.out.println("Target 构建方法!");
        }

        final public void noProxyMethod(){
            System.out.println("noProxyMethod 执行!");
        }

        public void proxyMethod(){
            System.out.println("proxyMethod 执行!");
        }
    }

    static class MyMethodInterceptor implements MethodInterceptor {

        public void before() {
            System.out.println("do something before!");
        }

        public void after() {
            System.out.println("do something after!");
        }

        public static <T> T getProxy(Class clazz){
            Enhancer enhacer = new Enhancer();
            // 设置enhancer对象的父类
            enhacer.setSuperclass(Target.class);
            // 设置enhancer的回调对象
            enhacer.setCallback(new MyMethodInterceptor());
            return (T) enhacer.create();
        }

        /**
         * sub:cglib生成的代理对象
         * method:被代理对象方法
         * objects:方法入参
         * methodProxy: 代理方法
         */
        @Override
        public Object intercept(Object sub, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            before();
            Object object = methodProxy.invokeSuper(sub, objects);
            after();
            return object;
        }

    }

    public static void main(String[] args){
        Target proxy = MyMethodInterceptor.getProxy(Target.class);
        //该方法无法使用CGLIB动态代理,因为使用了final参数修饰方法
        proxy.noProxyMethod();
        proxy.proxyMethod();
    }

}

执行结果:
Target 构建方法!
noProxyMethod 执行!
do something before!
proxyMethod 执行!
do something after!

多线程

创建线程的方式及实现
  • 继承Thread类创建线程类
  • 通过Runnable接口创建线程类
  • 通过Callable和Future创建线程

创建线程的三种方式的对比
采用实现Runnable、Callable接口的方式创见多线程时,优势是:线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。劣势是:编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。

多线程有几种状态,以及各种状态转换图是怎样的?

https://blog.csdn.net/weixin_40482816/article/details/110825755
https://www.jianshu.com/p/ec94ed32895f

sleep() 、wait()、join()、yield()有什么区别

①sleep() :让当前正在执行的线程在指定的时间内暂停执行,进入阻塞状态,该方法既可以让其他同优先级或者高优先级的线程得到执行的机会,也可以让低优先级的线程得到执行机会。但是sleep()方法不会释放“锁标志”,也就是说如果有synchronized同步块,其他线程仍然不能访问共享数据。

②wait():使当前线程暂停执行,并将当前线程放入对象等待池中,直到调用了notify()方法后,将从对象等待池中移出任意一个线程并放入锁标志等待池中,只有锁标志等待池中的线程可以获取锁标志,它们随时准备争夺锁的拥有权。当调用了某个对象的notifyAll()方法,会将对象等待池中的所有线程都移动到该对象的锁标志等待池。除了使用notify()和notifyAll()方法,还可以使用带毫秒参数的wait(long timeout)方法,效果是在延迟timeout毫秒后,被暂停的线程将被恢复到锁标志等待池。wait(),notify()及notifyAll()只能在synchronized语句中使用,但是如果使用的是ReenTrantLock实现同步,该如何达到这三个方法的效果呢?解决方法是使用ReenTrantLock.newCondition()获取一个Condition类对象,然后Condition的await(),signal()以及signalAll()分别对应上面的三个方法。

③yield():yield()方法和sleep()方法类似,也不会释放“锁标志”,区别在于,它没有参数,即yield()方法只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行,另外yield()方法只能使同优先级或者高优先级的线程得到执行机会,这也和sleep()方法不同

④join():join()方法会使当前线程等待调用join()方法的线程结束后才能继续执行

创建线程池的几种方式

newFixedThreadPool(int nThreads)
创建一个固定长度的线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数量,这时线程规模将不再变化,当线程发生未预期的错误而结束时,线程池会补充一个新的线程

newCachedThreadPool()
创建一个可缓存的线程池,如果线程池的规模超过了处理需求,将自动回收空闲线程,而当需求增加时,则可以自动添加新线程,线程池的规模不存在任何限制

newSingleThreadExecutor()
创建单个工作线程来执行任务,如果这个线程异常结束,会创建一个新的来替代它;它的特点是能确保依照任务在队列中的顺序来串行执行

newScheduledThreadPool(int corePoolSize)
创建了一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于Timer

说说线程安全问题

线程安全是指要控制多个线程对某个资源的有序访问或修改,而在这些线程之间没有产生冲突,java的实现方式有以下几种:

  • 最简单的方式,使用 Synchronization 关键字
  • 使用 java.util.concurrent.atomic 包中的原子类,例如 AtomicInteger
  • 使用 java.util.concurrent.locks 包中的锁
  • 使用线程安全的集合 ConcurrentHashMap
  • 使用 volatile 关键字,保证变量可见性(直接从内存读,而不是从线程 cache 读)

详细参考:https://blog.csdn.net/qq_40428665/article/details/121651421

悲观锁和乐观锁

悲观锁
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。

乐观锁
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了CAS实现的

CAS 乐观锁
乐观锁是一种思想,即认为读多写少,遇到并发写的可能性比较低,所以采取在写时先读出当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新),如果失败则要重复读­比较­写的操作。
CAS是一种更新的原子操作,比较当前值跟传入值是否一样,一样则更新,否则失败
CAS顶多算是乐观锁写那一步操作的一种实现方式罢了,不用CAS自己加锁也是可以的

ABA 问题
如果另一个线程修改V值,假设原来是A,先修改成B,再修改回成A,当前线程的CAS 操作无法分辨当前V值是否发生过变化。

谈谈你对AQS 的理解

AQS 是多线程同步器,它是 J.U.C 包中多个组件的底层实现,如Lock、CountDownLatch、Semaphore 等都用到了 AQS,从本质上来说,AQS 提供了两种锁机制,分别是排它锁,和共享锁。
排它锁,就是存在多线程竞争同一共享资源时,同一时刻只允许一个线程访问该共享资源,也就是多个线程中只能有一个线程获得锁资源,比如Lock 中的ReentrantLock 重入锁实现就是用到了 AQS 中的排它锁功能。
共享锁也称为读锁,就是在同一时刻允许多个线程同时获得锁资源,比如CountDownLatch 和 Semaphore 都是用到了 AQS 中的共享锁功能。


ThreadLocal的原理和使用场景

其他
https://www.jianshu.com/p/3c9f8d93d4de

JavaWeb

HTTP 请求的 GET 与 POST 方式的区别
  • GET请求参数放在请求头,POST参数放在请求体,因此敏感信息不可用GET请求传递
  • URL对字符数目有限制,所以GET请求参数有限制大小,而POST没有
session 与 cookie 区别
  • cookie存储在浏览器上,而session存储在服务器中
  • session不受浏览器限制,而cookie可能会被浏览器禁用,且每一个web服务器存储的cookie大小有限,一般不超过4KB
session的工作原理

用户第一次请求服务器时,服务器端会生成一个sessionid

服务器端将生成的sessionid返回给客户端,通过set-cookie

客户端收到sessionid会将它保存在cookie中,当客户端再次访问服务端时会带上这个sessionid

当服务端再次接收到来自客户端的请求时,会先去检查是否存在sessionid,不存在就新建一个sessionid重复1,2的流程,如果存在就去遍历服务端的session文件,找到与这个sessionid相对应的文件,文件中的键值便是sessionid,值为当前用户的一些信息

此后的请求都会交换这个 Session ID,进行有状态的会话。

MVC模式

MVC是模型(model)-视图(view)-控制器(controller)的缩写,是一个架构模式,它分离了表现与交互。

  • 视图是用户看到并与之交互的界面
  • 模型表示业务数据,并提供数据给视图
  • 控制器接受用户的输入并调用模型和视图去完成用户的需求
JDBC流程
//1.加载JDBC驱动程序
Class.forName("com.mysql.jdbc.Driver"); //反射

//2.建立连接(书写形式:协议:子协议:数据源标识)
Connection conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/数据库名称","用户名称","密码");

//3.创建一个Statement(对数据库发出请求)
//执行静态SQL语句。通常通过Statement实例实现
//执行动态SQL语句。通常通过PreparedStatement实例实现
//执行数据库存储过程。通常通过CallableStatement实例实现
String sql="select id,username,pwd from t_user where id>?";
PrepareStatement ps=conn.prepareStatement(sql);
ps.setObject(1, 2); //第一个"问号",传入2. -->把id大于2的记录都取出来
rs=ps.executeQuery();

//4.返回查询结果
ResultSet rs=ps.executeQuery();  //执行查询请求,并返回"结果集"

//5.关闭链接
if(rs!=null){ //RsultSet rs
   rs.close();
}
if(ps!=null){ //PreparedStatement ps
   ps.close();
}
if(conn!=null){ //connection conn
   conn.close();
}
Restful认识

Restful是一种面向资源的架构风格,可以简单理解为:使用URL定位资源,用HTTP动词(GET,POST,DELETE,PUT)描述操作,可以充分利用 HTTP 协议本身语义,不同请求方式进行不同的操作。

Get http://localhost:8080/employee/1   

Post http://localhost:8080/employee/1
{
}

delete http://localhost:8080/employee/1

put http://localhost:8080/employee
{
}

JVM

类加载

什么是类加载器,类加载器有哪些?

实现通过类的权限定名获取该类的二进制字节流的代码块叫做类加载器, 主要有一下四种类加载器:

  • 引导类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如rt.jar、charsets.jar等
  • 扩展类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的类包
  • 应用程序类加载器:负责加载ClassPath路径下的类包,即开发人员编写的代码
  • 自定义类加载器:用户自定义类加载器,通过继承 java.lang.ClassLoader类的方式实现,负责加载用户自定义路径下的类包
什么是双亲委派模型?

加载某个类时会先委托父加载器寻找目标类,找不到再委托上层父加载器加载,如果所有父加载器在自己的加载类路径下都找不到目标类,则在自己的类加载路径中查找并载入目标类。这样做的目的有以下两点:

①沙箱安全机制:自己编写的核心类库类不会被加载,防止核心API库被随意乱改
②避免类的重复加载:当父类已经加载了该类时,就没有必要子类加载器再加载一次,保证被加载类的唯一性

说一下类装载的执行过程?
  • 加载:根据查找路径找到相应的 class 文件然后导入;
  • 验证:检查加载的 class 文件的正确性;
  • 准备:给类中的静态变量分配内存空间;
  • 解析:虚拟机将常量池中的符号引用替换成直接引用的过程。符号引用就理解为一个标示,而在直接引用直接指向内存中的地址;
  • 初始化:对静态变量和静态代码块执行初始化工作。

内存模型

说一下 JVM 运行时数据区

Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存区域划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有些区域随着虚拟机进程的启动而存在,有些区域则是依赖线程的启动和结束而建立和销毁。
在这里插入图片描述

不同虚拟机的运行时数据区可能略微有所不同,但都会遵从 Java 虚拟机规范, Java 虚拟机规范规定的区域分为以下 5 个部分:

  • 程序计数器(Program Counter Register):当前线程所执行的字节码的行号指示器,字节码解析器的工作是通过改变这个计数器的值,来选取下一条需要执行的字节码指令;
  • Java 虚拟机栈(Java Virtual Machine Stacks):用于存储局部变量表、操作数栈、动态链接、方法出口等信息;
  • 本地方法栈(Native Method Stack):与虚拟机栈的作用是一样的,只不过虚拟机栈是服务 Java方法的,而本地方法栈是为虚拟机调用 Native 方法服务的;
  • Java 堆(Java Heap):Java 虚拟机中内存大的一块,是被所有线程共享的,几乎所有的对象实例都在这里分配内存;
  • 方法区(Methed Area):用于存储已被虚拟机加载的类信息、常量、静态变量等数据

直接内存:直接内存不是JVM运行时的数据区的一部分,也不是Java虚拟机规范中定义的内存区域。在JDK1.4中引入了NIO(New Input/Output)类,可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java中的DirectByteBuffer对象作为对这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java对和Native对中来回复制数据。

注意:
Jdk1.6及之前:有永久代, 常量池在方法区
Jdk1.7:有永久代,但已经逐步“去永久代”,常量池在堆
Jdk1.8及之后: 无永久代,常量池在堆
https://blog.csdn.net/weixin_44844089/article/details/117451603

说一下堆栈的区别?

存放的内容
堆存放的是对象的实例和数组,栈存放局部变量,操作数栈,返回结果。

程序的可见度
堆对于整个应用程序都是共享、可见的。栈只对于线程是可见的,是线程私有的,他的生命周期和线程相同。

对象创建

对象的创建流程

在这里插入图片描述
类加载检查
虚拟机遇到一个new指令的时候,会先到查看该类是否被加载、解析和初始化过,如果还没有,先执行类加载过程

内存分配
类加载完成后,对象所需的内存便可完全确定,为对象分配内存,就是将一块确定大小的内存从堆或者栈中分配出来。

划分内存的方法:
①指针碰撞:java堆内存已分配内存和未分配内存是分开的,中间有一个指针来分开,每次分配内存,只需要维护指针的位置
②空闲列表:java堆内存已分配内存和未分配内存是杂乱的分配在一起的,因此虚拟机通过维护一个列表,记录上哪些内存被使用哪些没被使用

解决并发问题的方法:
①CAS
②本地线程分配缓存:把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一小块内存。通过­XX:+/­ UseTLAB参数来设定虚拟机是否使用TLAB(JVM会默认开启­XX:+UseTLAB),­XX:TLABSize 指定TLAB大小

初始化
虚拟机将分配到的内存都设置为零值

设置对象头
对象在内存中的布局主要可以分为三块:对象头、实例数据和对齐填充,对象头主要对象的元数据信息、哈希码、对象的GC分代年龄等,对齐填充主要是将对象补齐成8的整数倍字节大小,方便存取
在这里插入图片描述
执行init方法
执行init方法为属性赋值

指针压缩
指针压缩就是jvm通过对对象的地址指针存入内存时进行压缩编码、取出到cpu寄存器时解码方式进行优化,使得使用32位地址指针通过解压缩就可以访问最多35位的地址,从而减少内存消耗,因此堆内存小于4g时,不需要使用指针压缩,大于32g时,指针压缩会失效,强制使用64位地址来对对象寻址

对象内存分配流程

在这里插入图片描述
逃逸分析
分析对象的作用于范围,如果一个对象在方法中被定义后,并没有被外部方法引用,那么就可以确定该对象不会逃逸,可以将对象分配在该方法对应的栈内存中,JDK1.7之后默认启动-XX:+DoEscapeAnalysis来开启逃逸分析

标量替换
JDK1.7之后,默认开启-XX:+EliminateAllocations,如果逃逸分析确认,该对象不会逃逸,JVM不会创建该对象,而是将该对象成员变量分解成若干个被这个方法使用的成员变量代替,这样就不会导致由于栈空间中没有一块大的连续空间而放不下该变量

大对象直接进入老年代
-XX:PretenureSizeThreshold可以设置大对象大小,如果需要大量连续内存空间的对象(如字符串、数组等)超过该值,则直接将该对象放入老年代,这样可以避免为大对象分配内存时的复制操作而降低效率。

长期存活的对象将进入老年代
MinorGC存活下来的对象会被放入到一块survivor中,并且将对象年龄设置为1,每一次Minor都会将对象年龄加1,当它的年龄增加到一定程度 (默认为15岁,CMS收集器默认6岁,不同的垃圾收集器会略微有点不同),就会被晋升到老年代中。对象晋升到老年代 的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置。

对象动态年龄判断
当前存放对象的survivor区域,如果年龄为1+2+…+n的对象已经超过了survivor的50%(-XX:TargetSurvivorRatio可以指定),此时就会把年龄n(含)以上的对象都放入老年代。这样可以将那些长期存活的对象今早的放入老年代,减少FullGC

老年代空间分配担保机制
在这里插入图片描述

对象的访问定位

Java程序需要通过 JVM 栈上的引用访问堆中的具体对象。对象的访问方式取决于 JVM 虚拟机的实现。目前主流的访问方式有 句柄 和 直接指针 两种方式。

句柄访问
Java堆中划分出一块内存来作为句柄池,引用中存储对象的句柄地址,而句柄中包含了对象实例数据与对象类型数据各自的具体地址信息,具体构造如下图所示:
在这里插入图片描述
优势:引用中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是 非常普遍的行为)时只
会改变句柄中的实例数据指针,而引用本身不需要修改。

直接指针
如果使用直接指针访问,引用中存储的直接就是对象地址,那么Java堆对象内部的布局中就必须考虑如何放置访问类型数据的相关信息。
在这里插入图片描述
优势:速度更快,节省了一次指针定位的时间开销。由于对象的访问在Java中非常频繁,因此这类开销积少成多后也是非常可观的执行成本。HotSpot 中采用的就是这种方式。

垃圾回收

简述Java垃圾回收机制

在java中,程序员是不需要显示的去释放一个对象的内存的,而是由虚拟机自行执行。在JVM中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫描那些没有被任何引用的对象(引用计数器或者可达性分析算法),并将它们添加到要回收的集合中,进行回收。

引用计数器

定义
给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加1;当引用失效,计数器就减1;任何时候计数器为0 的对象就是不可能再被使用的。

优缺点
这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决 对象之间相互循环引用的问题。

可达性分析算法

定义
将“GC Roots” 对象作为起点,从这些节点开始向下搜索引用的对象,找到的对象都标记为非垃圾对象,其余未标记的对象都是垃圾对象
GC Roots根节点:线程栈的本地变量、静态变量、本地方法栈的变量等等
在这里插入图片描述

JVM 有哪些垃圾回收算法?
  • 标记-清除算法:标记无用对象,然后进行清除回收。缺点:效率不高,无法清除垃圾碎片。
  • 复制算法:按照容量划分二个大小相等的内存区域,当一块用完的时候将活着的对象复制到另一块上,然后再把已使用的内存空间一次清理掉。缺点:内存使用率不高,只有原来的一半。
  • 标记-整理算法:标记无用对象,让所有存活的对象都向一端移动,然后直接清除掉端边界以外的内存。
  • 分代算法:根据对象存活周期的不同将内存划分为几块,一般是新生代和老年代,新生代基本采用复制算法,老年代采用标记整理算法。
JVM 有哪些垃圾回收器?

在这里插入图片描述
用于回收新生代的收集器 包括Serial、PraNew、Parallel Scavenge,回收老年代的收集器包括Serial Old、Parallel Old、CMS,还有用于回收整个Java堆的G1收集器:

  • Serial收集器(复制算法): 新生代单线程收集器,标记和清理都是单线程,优点是简单高效;
  • ParNew收集器 (复制算法): 新生代收并行集器,实际上是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现;
  • Parallel Scavenge收集器 (复制算法): 新生代并行收集器,追求高吞吐量,高效 利用 CPU。吞吐量= 用户线程时间/(用户线程时间+GC线程时间),高吞吐量可以高 效率的利用CPU时间,尽快完成程序的运算任务,适合后台应用等对交互相应要求不高的场景;
  • Serial Old收集器 (标记-整理算法): 老年代单线程收集器,Serial收集器的老年代版本;
  • Parallel Old收集器 (标记-整理算法): 老年代并行收集器,吞吐量优先, Parallel Scavenge收集器的老年代版本;
  • CMS(Concurrent Mark Sweep)收集器(标记-清除算法): 老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最短GC回收停顿时间。
  • G1(Garbage First)收集器 (标记-整理算法): Java堆并行收集器,G1收集器是 JDK1.7提供的一个新收集器,G1收集器基于“标记-整理”算法实现,也就是说不会 产生内存碎片。此外,G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。
详细介绍一下 CMS 垃圾回收器?

CMS 是英文 Concurrent Mark-Sweep 的简称,是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。在启动 JVM 的参数加上“-XX:+UseConcMarkSweepGC”来指定使用 CMS 垃圾回收器。 CMS 使用的是标记-清除的算法实现的,所以在 gc的时候回产生大量的内存碎片,当剩余内存不能满足程序运行要求时,系统将会出现Concurrent Mode Failure,临时 CMS 会采用 Serial Old 回收器进行垃圾清除,此时的性能将会被降低。

Java会存在内存泄漏吗?请简单描述

内存泄漏是指不再被使用的对象或者变量一直被占据在内存中。理论上来说, Java是有GC垃圾回收机制的,也就是说,不再被使用的对象,会被GC自动回收掉,自动从内存中清除。但是, 即使这样,Java也还是存在着内存泄漏的情况,java导致内存泄露的原因很明确:长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露, 尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是java中内存泄露的发生场景。

简述分代垃圾回收器是怎么工作的?

分代回收器有两个分区:老生代和新生代,新生代默认的空间占比总空间的 1/3,老生代的默认占比是2/3。新生代使用的是复制算法,新生代里有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1,它的执行流程如下:

  • 把 Eden + From Survivor 存活的对象放入 To Survivor 区;
  • 清空 Eden 和 From Survivor 分区;
  • From Survivor 和 To Survivor 分区交换,From Survivor 变 To Survivor,To Survivor 变 From Survivor。

每次在 From Survivor 到 To Survivor 移动时都存活的对象,年龄就 +1,当年龄到达 15(默认配置是15,CMS收集器默认6岁,不同的垃圾收集器会略微有点不同)时,升级为老生代。大对象也会直接进入老生代。老生代当空间占用到达某个值之后就会触发全局垃圾收回,一般使用标记整理的执行算法。以上这些循环往复就构成了整个分代垃圾回收的整体执行流程。

GC分哪几种?什么时候会触发Full GC?

Minor GC
清理整个YouGen的过程,eden的清理,S0\S1的清理都会由于MinorGC Allocation Failure(YoungGen
区内存不足),而触发minorGC

Major GC
OldGen区内存不足,触发Major GC

Full GC
Full GC 是清理整个堆空间—包括年轻代和永久代

Full GC 触发的场景
1)System.gc
2)promotion failed (年代晋升失败,比如eden区的存活对象晋升到S区放不下,又尝试直接晋升到Old区又放不下,那么Promotion Failed,会触发FullGC)
3)CMS的Concurrent-Mode-Failure
4)新生代晋升的平均大小大于老年代的剩余空间 (为了避免新生代晋升到老年代失败)

Java 中都有哪些引用类型?
  • 强引用:普通的变量引用
  • 软引用:将对象用SoftReference软引用类型的对象包裹,正常情况不会被回收,但是GC做完后发现释放不出空间存放新的对象,则会把这些软引用的对象回收掉。软引用可用来实现内存敏感的高速缓存
  • 弱引用:将对象用WeakReference软引用类型的对象包裹,弱引用跟没引用差不多,GC会直接回收掉,很少用
  • 虚引用:将对象用PhantomReference软引用类型的对象包裹,无法通过虚引用获得对象,它是最弱的一种引用关系,几乎不用

JVM调优

常用的 JVM 调优的参数都有哪些?
  • -Xms2g:初始化推大小为 2g;
  • -Xmx2g:堆最大内存为 2g;
  • -XX:NewRatio=4:设置年轻的和老年代的内存比例为 1:4;
  • -XX:SurvivorRatio=8:设置新生代Eden 和 Survivor 比例为 8:2;
  • –XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器组合;
  • -XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器组合;
  • -XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器组合;
  • -XX:+PrintGC:开启打印 gc 信息;
  • -XX:+PrintGCDetails:打印 gc 详细信息。
JVM性能调优工具有哪些

① Jmap

  • 查看内存信息,实例个数以及占用内存大小
jmap -histo 14660  #查看历史生成的实例
jmap -histo:live 14660  #查看当前存活的实例,执行过程中可能会触发一次full gc
  • 查看堆信息,或者生成堆内存dump快照,然后使用jvisualvm查看
jmap -dump:format=b,file=eureka.hprof 14660

② Jstack:用jstack加进程id查找死锁

③ Jinfo:查看正在运行的Java应用程序的扩展参数

④ Jstat:jstat -gc pid 最常用,可以评估程序内存使用及GC压力整体情况

⑤ jvisualvm:JDK 自带的全能分析工具,可以分析:内存快照、线程快照、程序死锁、监控内存的变化、gc 变化等。

Mysql

基础知识

数据库三大范式是什么

第一范式(1NF):原子性,字段不可分
数据表的每个列都要具有原子性,不可被分割,否则就不是关系型数据库

第二范式(2NF):唯一性,有主键,非主键字段依赖主键
一个数据库表只能描述一个事物,数据表中所有非主键字段都要依赖唯一主键,满足第二范式前必须先满足第一范式

第三范式(3NF):非主键字段不能相互依赖
每列都与主键有直接关系,不存在传递依赖,一个数据库表中不包含已在其它表中已包含的非主键字段,满足第三范式前必须先满足第二范式

Mysql有关权限的表都有哪几个

MySQL服务器通过权限表来控制用户对数据库的访问,权限表存放在mysql数据库里,由mysql_install_db脚本初始化。

  • user权限表:记录允许连接到服务器的用户帐号信息,里面的权限是全局级的。
  • db权限表:记录各个帐号在各个数据库上的操作权限。
  • table_priv权限表:记录数据表级的操作权限。
  • columns_priv权限表:记录数据列级的操作权限。
  • host权限表:配合db权限表对给定主机上数据库级操作权限作更细致的控制。这个权限表不受GRANT和REVOKE语句的影响。
Mysql的binlog有几种录入格式?分别有什么区别?
  • statement模式下,每一条会修改数据的sql都会记录在binlog中。不需要记录每一行的变化,减少了binlog日志量,节约了IO,提高性能。由于sql的执行是有上下文的,因此在保存的时候需要保存相关的信息,同时还有一些使用了函数之类的语句无法被记录复制。
  • row级别下,不记录sql语句上下文相关信息,仅保存哪条记录被修改。记录单元为每一行的改动,基本是可以全部记下来但是由于很多操作,会导致大量行的改动(比如altertable),因此这种模式的文件保存的信息太多,日志量太大。
  • mixed,一种折中的方案,普通操作使用statement记录,当无法使用statement的时候使用row。

此外,新版的MySQL中对row级别也做了一些优化,当表结构发生变化的时候,会记录语句而不是逐行记录。

mysql有哪些数据类型

Mysql主要有4种数据类型,分别是数值数据类型、日期和时间类型、字符串类型、二进制类型。

数值数据类型

整数类型
整数类型
浮点数类型和定点数类型
浮点数类型和定点数类型

取值范围

float存储方式1bit(符号位)8bits(指数位)23bits(尾数位)
double存储方式1bit(符号位)11bits(指数位)52bits(尾数位)

因此,float的指数范围为-127 ~ +128,而double的指数范围为-1023 ~ +1024,并且指数位是按补码的形式来划分的。其中负指数决定了浮点数所能,表达的绝对值最小的非零数;而正指数决定了浮点数所能表达的绝对值最大的数,也即决定了浮点数的取值范围。float的范围为-2^128 ~ +2^128,也即-3.40E+38 ~ +3.40E+38;double的范围为-2^1024 ~ +2^1024,也即-1.79E+308 ~ +1.79E+308。

注意点
float(m, d),m指定有效位数(包括整数位和小数位),d指定精确位数,d最多可为6位

float和double和decimal需要怎么选
1 如果你要表示的浮点型数据转成二进制之后能被32位float存储,或者可以容忍截断,则使用float,这个范围大概为要精确保存6位数字左右的浮点型数据
比如10分制的店铺积分可以用float存储,小商品零售价格(1000块之内)

2 如果你要表示的浮点型数据转成二进制之后能被64位double存储,或者可以容忍截断,这个范围大致要精确到保存13位数字左右的浮点型数据
比如汽车价格,几千万的工程造价

3 相比double,已经满足我们大部分浮点型数据的存储精度要求,如果还要精益求精,则使用decimal定点型存储
比如一些科学数据,精度要求很高的金钱

日期类型

日期与时间类型

字符串类型与二进制类型

在这里插入图片描述

事务

什么是数据库事务?

事务是逻辑上的一组操作,要么都执行,要么都不执行。

事务的四大特性(ACID)介绍一下?

关系性数据库需要遵循ACID规则,具体内容如下:

原子性:事务中包含的所有操作,要么全部成功执行,要么失败全部回滚。

一致性:事务执行前后,数据库必须处于一致性状态
PS:假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。

隔离性:隔离性是指在并发操作中,不同事务之间应该按照设定的隔离级别隔离开来,使每个并发中的事务不会相互干扰。

持久性:一旦事务提交成功,事务中所有的数据操作都必须被持久化到数据库中,即使提交事务后,数据库马上崩溃,在数据库重启时,也必须能保证通过某种机制恢复数据。

事务隔离级别

Read uncommitted (读未提交)
实质:

  • 如果A事务读取数据,那么其他事务可以任意操作该数据
  • 如果A事务已经开始写数据了,那么不允许其他事务进行写操作,但是可以读取A事务还未提交的数据

问题:如果B事务读取了A事务操作的数据后,A事务进行了回滚,那么B事务读取的数据就会变成脏数据,从而造成脏读

Read committed (读已提交)
实质:

  • 如果A事务读取数据,那么其他事务可以任意操作该数据,且已提交的事务的修改才可被读取
  • 如果A事务已经开始写数据了,那么不允许其他事务进行读写操作,只有A事务提交了事务,其他事务才可读写A事务操作的数据

问题:如果B事务先读取了数据,然后A事务写B读取的数据,然后提交事务后,B事务再次读取对应数据,那么就会出现两次读取的数据不一致的问题,从而造成不可重复读

Repeatable read (可重复读)
实质:

  • 如果A事务读取了数据,那么其他事务可以任意操作该数据,但是A事务每次读取的行的数据不变,也就是可重复读
  • 如果A事务已经开始了写数据,那么不允许其他事务进行读写操作,只有A事务提交了事务,其他事务才可读写A事务操作的数据

问题:如果A事务先读取了表一的数据,然后B事务删除了A事务读取的记录并提交了事务,此时A事务对于B事务删除的记录的修改删除都不生效,但是查询出来的数据还是有B事务删除的记录

Serializable(串行化)
实质:读数据和写数据都会加锁,因此每次读取的数据都会是一样的,解决了幻读的问题

隔离级别问题

脏读
脏读指的是读到了其他事务未提交的数据,未提交意味着这些数据可能会回滚,也就是可能最终不会存到数据库中,也就是不存在的数据。读到了并一定最终存在的数据,这就是脏读。

不可重复读
在一个事务内,最开始读到的数据和事务结束前的任意时刻读到的同一批数据是不一致的。通常针对数据更新(UPDATE)操作。

虚读(幻读)
如果A事务先读取了表一的数据,然后B事务删除了A事务读取的记录并提交了事务,此时A事务对于B事务删除的记录的修改删除都不生效,但是查询出来的数据还是有B事务删除的记录

引擎

MySQL存储引擎MyISAM与InnoDB区别

在这里插入图片描述

存储引擎选择

如果没有特别的需求,使用默认的Innodb即可。
MyISAM:以读写插入为主的应用程序,比如博客系统、新闻门户网站。
Innodb:更新(删除)操作频率也高,或者要保证数据的完整性;并发量高,支持事务和外键。比如OA自动化办公系统。

索引

什么是索引?

数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。索引的实现通常使用B树及其变种B+树。更通俗的说,索引就相当于目录。为了方便查找书中的内容,通过对内容建立索引形成目录。索引是一个文件,它是要占据物理空间的。

索引有哪几种类型?
  • 主键索引:数据列不允许重复,不允许为NULL,一个表只能有一个主键。
  • 唯一索引:数据列不允许重复,允许为NULL值,一个表允许多个列创建唯一索引。
  • 普通索引:基本的索引类型,没有唯一性的限制,允许为NULL值。
  • 全文索引:是目前搜索引擎使用的一种关键技术。
索引的数据结构(b树,hash)
数据结构

Mysql的索引数据结构主要有B+Tree、Hash表两种

B-Tree特点

  • 叶节点具有相同的深度,叶节点的指针为空
  • 所有索引元素不重复
  • 节点中的数据索引从左到右递增排序
    在这里插入图片描述

B+Tree特点

  • 非叶子节点不存储data,只存储索引,因此会导致数据冗余,但是可以放更多的索引
  • 叶子节点包含所有的索引字段
  • 叶子节点用指针连接,提高区间访问的性能
    在这里插入图片描述

Hash表的特点

  • 对索引的key进行一次hash计算就可以定位出数据存储的位置
  • 很多时候Hash索引要比B+树更加高效
  • 仅能满足"="、“IN”,不支持范围查询
  • hash冲突问题

在这里插入图片描述

实现方式

MyISAM索引
文件和数据文件是分离的(非聚集)
在这里插入图片描述
InnoDB主键索引
数据和索引是在同一个文件(聚集)

  • 表数据文件本身就是按B+Tree组织的一个索引结构文件
  • 聚集索引-叶节点包含了完整的数据记录
    在这里插入图片描述
    InnoDB非主键索引
    InnoDB非主键索引的叶子节点的data中,只存主键地址(聚集)
    在这里插入图片描述
    InnoDB联合(复合)索引
    联合索引:就是一个索引由多个字段组成,需要满足最左前缀原则
    在这里插入图片描述
创建索引的原则

索引虽好,但也不是无限制的使用,最好符合一下几个原则:

① 较频繁作为查询条件的字段才去创建索引,更新频繁字段不适合创建索引

② 若是不能有效区分数据的列不适合做索引列(如性别,男女未知,最多也就三种,区分度实在太低)

③ 尽量的扩展索引,不要新建索引

④ 定义有外键的数据列一定要建立索引

⑤ 对于定义为text、image和bit的数据类型的列不要建立索引

百万级别或以上的数据如何删除
  1. 所以我们想要删除百万数据的时候可以先删除索引(此时大概耗时三分多钟)
  2. 然后删除其中无用数据(此过程需要不到两分钟)
  3. 删除完成后重新创建索引(此时数据较少了)创建索引也非常快,约十分钟左右。

优化

SQL语句的优化

① 分页查询时,优化LIMIT M,N

  • 使用索引覆盖+子查询优化
select id,name from table_name where id in (select id from table_name order by id limit 866612, 1)
  • 起始位置重定义(效率最高,但是要记住ID值)
select id,name from table_name where id > 866612 limit 20

② 禁止不必要的Order By排序
③ 将多次插入换成批量Insert插入
④ 只返回必要的列,用具体的字段列表代替 select * 语句
⑤ 区分in和exists,如果是exists,那么以外层表为驱动表,先被访问,如果是IN,那么先执行子查询
⑥ 尽量使用数字型字段
⑦ 优化Group By语句

  • 如果对group by语句的结果没有排序要求,要在语句后面加 order by null(group 默认会排序);
  • 尽量让group by过程用上表的索引,确认方法是explain结果里没有Using temporary 和 Using filesort;
  • 使用where子句替换Having子句:避免使用having子句,having只会在检索出所有记录之后才会对结果集进行过滤,这个处理需要排序分组,如果能通过where子句提前过滤查询的数目,就可以减少这方面的开销。

⑨ 优化Join语句

  • 用小结果集驱动大结果集
  • 对被驱动表的join字段上建立索引,当被驱动表的join字段上无法建立索引的时候,设置足够的Join Buffer Size
  • 尽量用inner join(因为其会自动选择小表去驱动大表)
  • 适当地在表里面添加冗余信息来减少join的次数
索引优化等级有哪些?

const, system:用于 primary key 或 unique key 的所有列与常数比较时,所以表最多有一个匹配行,读取1次,速度比较快。system是 const的特例,表里只有一条元组匹配时为system。

eq_ref:primary key 或 unique key 索引的所有部分被连接使用 ,最多只会返回一条符合条件的记录。

ref:相比 eq_ref,不使用唯一索引,而是使用普通索引或者唯一性索引的部分前缀,索引要和某个值相比较,可能会找到多个符合条件的行。

range:范围扫描通常出现在 in(), between ,> ,<, >= 等操作中。使用一个索引来检索给定范围的行。

index:扫描全索引就能拿到结果,一般是扫描某个二级索引,这种扫描不会从索引树根节点开始快速查找,而是直接对二级索引的叶子节点遍历和扫描,速度还是比较慢的,这种查询一般为使用覆盖索引,二级索引一般比较小,所以这种通常比ALL快一些。

ALL:即全表扫描,扫描你的聚簇索引的所有叶子节点。通常情况下这需要增加索引来进行优化了。

索引优化
  • 查询要符合最佳左前缀法则
  • 不在索引列上做任何操作(加减乘除或者函数操作)
  • 使用前缀索引
  • 尽量只查询覆盖索引中的列信息
  • 在组合/联合索引中,将有区分度的索引放在前面
  • mysql在使用负向查询条件(!=、<>、not in、not exists、not like)的时候无法使用索引会导致全表扫描
  • like 以通配符开头(%abc…)时,mysql索引失效会变成全表扫描的操作
  • 少用or,在 WHERE 子句中,如果在 OR 前的条件列是索引列,而在 OR 后的条件列不是索引列,那么索引会失效
  • SQL 性能优化 explain 中的 type:至少要达到 range 级别,要求是 ref 级别,如果可以是 consts 最好

其他
Mysql如何保证一致性的,undo文件

Innodb引擎SQL执行的BufferPool缓存机制

Spring

基础介绍

Spring介绍

Spring是⼀个 轻量级的控制反转和⾯向切⾯的容器 框架,⽤来解决企业项⽬开发的复杂度问题—解耦,具有以下特点:

  • 轻量级:体积⼩,对代码侵⼊性小
  • 控制反转:IoC(Inverse of Control),把创建对象的⼯作交由Spring完成,Spring在创建对象的时候同时可以完成对象属性赋值(DI)
  • ⾯向切⾯:AOP(Aspect Oriented Programming)⾯向切⾯编程,可以在不改变原有业务逻辑的情况下实现对业务的增强
  • 容器:实例的容器,管理创建的对象的生命周期
核心组件常用模块

在这里插入图片描述

在这里插入图片描述

Spring IOC

什么是Spring IOC,有什么作用

将传统的由new的方式创建并使用对象的方式改为通过注解或者xml配置对象信息,然后由Spring IOC容器来创建,在需要用到对象的业务代码类中,可以通过DI来管理对象的注入,同时Spring IOC还管理着容器中的Bean的生命周期。

Spring IOC 的实现机制是什么?

Spring 中的 IOC 的实现原理总的来说是工厂模式加反射机制,Spring通过读取xml或者注解配置的bean信息并包装成bean定义,然后BeanFactory通过bean定义里的相应信息,比如类名、类路径等通过反射来创建对象。

什么是DI,IOC和DI的区别是什么

DI就是为了管理程序间的各种依赖关系而产生的,我们可以将依赖关系定义为接口,然后通过@Autowired等注解来将业务代码中需要的对象注入到对应的引用中,实现代码解耦,而IOC的作用范围包括了创建对象、管理对象等,因此,从某种意义上来说,DI是对IOC思想的一种实现。

BeanDefinition的作用

它主要负责存储Bean的定义信息:决定Bean的生产方式。

<bean class="com.tuling.User" id="user" scope="singleton" lazy="false" abstract="false" autowire="none" ....>
	<property name="username" value="xushu">
</bean>

后续BeanFactory根据这些信息就进行生产Bean: 比如实例化可以通过class进行反射进而得到实例对象 , 比如lazy则不会在IOC加载时创建Bean。

BeanFactory的作用
  • BeanFactory是Spring中非常核心的一个顶层接口,主要职责就是生产Bean;
  • BeanFactory有非常多的实现类、每个工厂都有不同的职责(单一职责)功能,最强大的工厂是:DefaultListableBeanFactory
BeanFactory 和 ApplicationContext有什么区别?

BeanFactory根据名字就可以知道,他是一个生产bean的工厂,管理bean的加载,实例化,控制bean的生命周期。而ApplicationContext继承自BeanFactory,是应用上下文,因此自然能够实现提供BeanFactory的相关功能,不过底层还是调用了BeanFactory的一个实现类来提供bean工厂的相关功能,除了这部分功能外,ApplicationContext还具有国际化、资源加载、事件发布等功能

BeanFactory 和FactoryBean有什么区别?

BeanFactory是一个工厂,也就是一个容器,是来管理和生产bean的;

FactoryBean是一个接口,一个类如果继承了该接口,那么通过BeanFactory获取该类的bean对象或者类型时,会调用该类继承自FactoryBean的getObject()和getObjectType()两个方法,如果想在getbean时绕过getObject方法,可以使用&符号,还有一个注意点就是继承了FactoryBean的bean默认会使用懒加载。

IOC容器启动时,为什么先加载BeanFactoryPostProcess

因为BeanFactoryPostProcess就是对BeanDefinition做扩展的,所以要先加载BeanFactoryPostProcess,比如解析配置类的组件它就实现BeanFactoryPostProcess。

IOC容器的加载过程

1、创建bean工厂

2、实例化reader和scanner,在实例化reader时注册spring内部的BeanFactory后置处理器的BeanDefiniton

3、注册配置类的BeanDefinition

4、invokeBeanFactoryPostProcessors,这个方法会先实例化bean定义map中的BeanFactoryPostProcessors后置处理器,并且使用其中的ConfigurationClassPostProcessor后置处理器解析我们的配置类,解析配置类上的@ComponentScan,@Import,注册所有扫描到的bean

5、注册Bean的后置处理器

7、实例化bean

8、属性赋值

9、初始化bean

3个Aware,@PostConstruct,实现 InitializedBean,init-method

10、进行aop处理

11、将bean实例加入一级缓存map中


你知道Spring的哪些扩展点,在什么时候调用?

Spring Beans

什么是Spring beans?

bean是一个由Spring IoC容器实例化、组装和管理的对象。

配置Bean有哪几种方式?
  • xml:
  • 注解:@Component(@Controller 、@Service、@Repostory) 前提:需要配置扫描包 反射调用构造方法
  • javaConfig: @Bean 可以自己控制实例化过程
  • @Import 3种方式
bean的作用域有哪几种

Spring框架支持以下五种bean的作用域:

  • singleton : bean在每个IOC 容器中只有一个实例。
  • prototype:一个bean的定义可以有多个实例。
  • request:每次http请求都会创建一个bean。
  • session:在一个HTTP Session中,一个bean定义对应一个实例。
  • grobal-session:全局 Web 应用程序范围内,一个bean定义对应一个实例。
Spring实例化bean有几种方式
  • 构造器方式(反射);
  • 静态工厂方式; factory-method
  • 实例工厂方式(@Bean); factory-bean+factory-method
  • FactoryBean方式
单例bean的优势
  • 减少了新生成实例的消耗新生成实例消耗包括两方面,第一,spring会通过反射或者cglib来生成bean实例这都是耗性能的操作,其次给对象分配内存也会涉及复杂算法。 提供服务器内存的利用率 ,减少服务器内存消耗
  • 减少jvm垃圾回收由于不会给每个请求都新生成bean实例,所以自然回收的对象少了。
  • 可以快速获取到bean因为单例的获取bean操作除了第一次生成之外其余的都是从缓存里获取的所以很快。
Spring如何处理线程并发问题?
  • 将Bean设置为多例对象
  • 将成员变量绑定在ThreadLocal中
  • 将成员变量放在方法中
  • 使用同步锁来对成员变量的读写操作进行限制,会影响系统吞吐量
什么是bean装配?什么是bean的自动装配?

bean的装配就是定义bean之间的依赖关系,比如通过标签下的标签来注入属性,而bean的自动装配就是使用一些注解,比如@Autowired来让Spring根据特定规则来对属性进行注入。

自动装配有哪些限制?
  • 一定要声明set方法
  • 覆盖: 你仍可以用 < constructor-arg >和 < property > 配置来定义依赖,这些配置将始终覆盖自动注入。
  • 基本数据类型:不能自动装配简单的属性,如基本数据类型、字符串和类。 (手动注入还是可以注入基本数据类型的 <property value="" @Value)
  • 模糊特性:自动装配不如显式装配精确,如果有可能尽量使用显示装配。
Bean的生命周期
  • 实例化Bean对象,这个时候Bean的对象是非常低级的,基本不能够被我们使用,因为连最基本的属性都没有设置,可以理解为连Autowired注解都是没有解析的;
  • 填充属性,当做完这一步,Bean对象基本是完整的了,可以理解为Autowired注解已经解析完毕,依赖注入完成了;
  • 如果Bean实现了BeanNameAware接口,则调用setBeanName方法;
  • 如果Bean实现了BeanClassLoaderAware接口,则调用setBeanClassLoader方法;
  • 如果Bean实现了BeanFactoryAware接口,则调用setBeanFactory方法;
  • 调用BeanPostProcessor的postProcessBeforeInitialization方法;
  • 如果Bean实现了InitializingBean接口,调用afterPropertiesSet方法;
  • 如果Bean定义了init-method方法,则调用Bean的init-method方法;
  • 调用BeanPostProcessor的postProcessAfterInitialization方法;当进行到这一步,Bean已经被准备就绪了,一直停留在应用的上下文中,直到被销毁;
  • 如果应用的上下文被销毁了,如果Bean实现了DisposableBean接口,则调用destroy方法,如果Bean定义了destory-method声明了销毁方法也会被调用。

如何在Spring所有BeanDefinition注册完后做扩展?
如何在Spring所有Bean创建完后做扩展?
Spring是如何解决Bean的循环依赖?
Spring如何避免在并发下获取不完整的Bean?

Spring注解

常用注解

在这里插入图片描述
在这里插入图片描述

Spring有哪几种配置方式
  • XML配置文件,spring诞生就有了
  • 基于注解的配置(Spring2.5+, @Component @Autowired)
  • 基于java的配置(Spring3.0+, @Configuration @Bean)
@Import可以有几种用法?
  • 直接指定类
  • 通过ImportSelector 可以一次性注册多个,返回一个string[] 每一个值就是类的完整类路径
  • 通过DeferredImportSelector可以一次性注册多个,返回一个string[] 每一个值就是类的完整类路径,DeferredImportSelector 顺序靠后
  • 通过ImportBeanDefinitionRegistrar 可以一次性注册多个,通过BeanDefinitionRegistry来动态注册BeanDefintion
@Autowired 注解有什么作用

@Autowired 是一个注释,它可以对类成员变量、方法及构造函数进行标注,让 spring 根据类型完成 bean 自动装配的工作。
@Autowired 默认是按照类型去匹配,配合 @Qualifier 可以指定按照名称去装配 bean。

@Autowired和@Resource之间的区别

@Autowired默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设置它required属性为false)
@Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入

@Bean之间的方法调用是怎么保证单例的?
  • 如果希望@bean的方法返回是对象是单例 需要在类上面加上@Configuration,这样就会为配置类生成动态代理
  • 当@Bean方法进行互调时, 则会通过CGLIB进行增强,通过调用的方法名作为bean的名称去ioc容器中获取,进而保证了@Bean方法的单例
为什么@ComponentScan 不设置basePackage也会扫描?

因为Spring在解析@ComponentScan的时候拿到basePackage如果没有设置会将你的类所在的包的地址作为扫描包的地址


要将一个第三方的类配成为Bean有哪些方式?
使用@Autowired注解自动装配的过程是怎样的?
配置类@Configuration的作用解析原理
用过JavaConfig方式的spring配置吗?它是如何替代xml的?
@Component, @Controller, @Repository, @Service 有何区别?

Spring AOP

什么是AOP、能做什么

AOP(Aspect-Oriented Programming),一般称为面向切面编程,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理等。

解释一下Spring AOP里面的几个名词
  • 切面(Aspect):在Spring Aop指定就是“切面类” ,切面类会管理着切点、通知。
  • 连接点(Join point):指定就是被增强的业务方法
  • 通知(Advice): 就是需要增加到业务方法中的公共代码,通知有很多种,包括前置通知、后置通知、异常通知、返回通知、环绕通知,因此通知的设定包括公共代码也包括执行位置
  • 切点(Pointcut): 由他决定哪些方法需要增强、哪些不需要增强, 结合切点表达式进行实现
  • 目标对象(Target Object): 指定是增强的对象
  • 织入(Weaving) : spring aop用的织入方式:动态代理。 就是为目标对象创建动态代理的过程就叫织入。
Spring通知的执行顺序?

在这里插入图片描述

什么情况下AOP会失效,怎么解决?
  • 方法是private 也会失效,解决:改成public
  • 目标类没有配置为Bean也会失效, 解决:配置为Bean
  • 内部调用不会触发AOP,解决:设置@EnableAspectJAutoProxy(exposeProxy = true),通过AopContext.currentProxy() 拿到当前正在调用的动态代理对象,再进行调用内部方法
JDK动态代理和CGLIB动态代理的区别

Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理:
①JDK动态代理只提供接口的代理,不支持类的代理。

  • JDK会在运行时为目标类生成一个动态代理类$proxy*.class
  • 该代理类是实现了接目标类接口, 并且代理类会实现接口所有的方法增强代码
  • 调用时 通过代理类先去调用处理类进行增强,再通过反射的方式进行调用目标方法。从而实现AOP

②如果代理类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。

  • CGLIB的底层是通过ASM在运行时动态的生成目标类的一个子类,还有其他相关类,主要是为增强调用时效率
  • 并且会重写父类所有的方法增强代码
  • 调用时先通过代理类进行增强,再直接调用父类对应的方法进行调用目标方法。从而实现AOP
  • CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的
JavaConfig方式如何启用AOP?如何强制使用cglib?
@EnableAspectJAutoProxy
//(proxyTargetClass = true) //强制CGLIB
//(exposeProxy = true) 在线程中暴露代理对象,然后通过AopContext.currentProxy(),来获取当前类的代理对象,实现调用本类方法时也可以进行动态代理

Spring的AOP是在哪里创建的动态代理?

Spring的 Aop的完整实现流程?

Spring事务

说一下Spring的事务传播行为

事务的传播特性指的是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行?
在这里插入图片描述

SpringMVC

Spring MVC的控制器是不是单例模式,如果是,有什么问题,怎么解决?

是单例模式,所以在多线程访问的时候有线程安全问题,不要用同步,会影响性能的,解决方案是在控制器里面不能写字段。

请描述Spring MVC的工作流程?
  • 用户发送请求至前端控制器DispatcherServlet;
  • DispatcherServlet收到请求后,调用HandlerMapping处理器映射器,请求获取Handle;
  • 处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet;
  • DispatcherServlet 调用 HandlerAdapter处理器适配器;
  • HandlerAdapter 经过适配调用 具体处理器(Handler,也叫后端控制器);
  • Handler执行完成返回ModelAndView;
  • HandlerAdapter将Handler执行结果ModelAndView返回给DispatcherServlet;
  • DispatcherServlet将ModelAndView传给ViewResolver视图解析器进行解析;
  • ViewResolver解析后返回具体View;
  • DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)
  • DispatcherServlet响应用户。

在这里插入图片描述

SpringMVC的拦截器和过滤器有什么区别?执行顺序?
  • 拦截器不依赖与servlet容器,过滤器依赖与servlet容器。
  • 拦截器只能对action请求(DispatcherServlet 映射的请求)起作用,而过滤器则可以对几乎所有的请求起作用。
  • 拦截器可以访问容器中的Bean(DI),而过滤器不能访问(基于spring注册的过滤器也可以访问容器中的bean)。
    在这里插入图片描述
说说你是如何解决 get 和 post 乱码问题?

① 解决post请求乱码问题:在web.xml中配置一个CharacterEncodingFilter过滤器,设置成utf­8;

<filter>
	<filter‐name>CharacterEncodingFilter</filter‐name>
	<filter‐class>org.springframework.web.filter.CharacterEncodingFilter</filter‐class>
	<init‐param>
	<param‐name>encoding</param‐name>
	<param‐value>utf‐8</param‐value>
	</init‐param>
</filter>

<filter‐mapping>
	<filter‐name>CharacterEncodingFilter</filter‐name>
	<url‐pattern>/*</url‐pattern>
</filter‐mapping>

② get请求中文参数出现乱码解决方法有两个:

  • 修改tomcat配置文件添加编码与工程编码一致,如下:
 <ConnectorURIEncoding="utf‐8" connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/>
  • 另外一种方法对参数进行重新编码(ISO8859­1是tomcat默认编码,需要将tomcat编码后的内容按utf­8编码):
String userName = new String(request.getParamter("userName").getBytes("ISO8859‐1"),"utf‐8")

SpringBoot

什么是SpringBoot,有哪些特性?

SpringBoot的用来快速开发Spring应用的一个脚手架、其设计目的是用来简新Spring应用的初始搭建以及开发过程。

  • SpringBoot提供了很多内置的Starter结合自动配置,对主流框架无配置集成、开箱即用。
  • SpringBoot简化了开发,采用JavaConfig的方式可以使用零xml的方式进行开发;
  • SpringBoot内置Web容器无需依赖外部Web服务器,省略了Web.xml,直接运行jar文件就可以启动web应用;
  • SpringBoot帮我管理了常用的第三方依赖的版本,减少出现版本冲突的问题;
Spring和SpringBoot的关系和区别?

SpringBoot是Spring生态的产品,而Spring Framework是一个容器框架,SpringBoot 它不是一个框架、它是一个可以快速构建基于Spring的脚手架(里面包含了Spring和各种框架),为开发Spring生态其他框架铺平道路。

SpringBoot的核心注解

@SpringBootApplication注解:这个注解标识了一个SpringBoot工程

@SpringBootConfiguration:这个注解实际就是一个@Configuration

@EnableAutoConfiguration:向Spring容器中导入了一个Selector,用来加载ClassPath下SpringFactories中所定义的自动配置类,将这些自动加载为配置Bean

@Conditional 也很关键, 如果没有它我们无法在自定义应用中进行定制开发

为什么SpringBoot的jar可以直接运行?

SpringBoot提供了一个插件spring-boot-maven-plugin用于把程序打包成一个可执行的jar包。

SpringBoot应用打包之后,生成一个Fat jar(jar包中包含jar),包含了应用依赖的jar包和Spring Boot loader相关的类

java -jar会去找jar中的manifest文件,在那里面找到真正的启动类(Main-Class)

Fat jar的启动Main函数是JarLauncher,它负责创建一个LaunchedURLClassLoader来加载boot-lib下面的jar,并以一个新线程启动应用的启动类的Main函数(找到manifest中的Start-Class)

springboot的自动配置原理?
  • 通过@SpringBootConfiguration 引入了@EnableAutoConfiguration (负责启动自动配置功能)
  • @EnableAutoConfiguration 引入了@Import
  • Spring容器启动时:加载Ioc容器时会解析@Import 注解
  • @Import导入了一个deferredImportSelector(它会使SpringBoot的自动配置类的顺序在最后,这样方便我们扩展和覆盖?)
  • 然后读取所有的/META-INF/spring.factories文件(SPI)
  • 过滤出所有AutoConfigurtionClass类型的类
  • 最后通过@ConditionOnXXX排除无效的自动配置类
SpringBoot配置文件加载顺序是怎样的?
目录加载顺序
① 命令行指定目录(java -jar xx.jar --spring.config.location=D:\config/)
PS:使用spring.config.location的方式指定配置文件,不会进行互补
② classpath根目录/config下
③ classpath根目录下(即resource下)
profile指定激活文件顺序
① 命令行指定激活profile(java -jar xx.jar --spring.profiles.active=dev)
② 配置文件指定激活profile
文件加载顺序
application.yml > application.yaml > application.properties
会不会SpringBoot自定义Starter?大概实现过程?
  • 创建一个项目工程,一般是创建一个xxx-spring-boot-starter,和一个xxx-spring-boot-autoconfigurer
  • 在autoconfigurer工程中可以创建HelloProperties,HelloController等类,然后添加HelloAutoConfiguration,设置自定义的启动器自动加载逻辑,比如可以通过 @ConditionalXXX来控制一些控制器的加载
  • 在 resources 下创建文件夹 META-INF 并在 META-INF 下创建文件 spring.factories,将自定义的自动配置类编写到文件中,以key-value的形式存在,key为org.springframework.boot.autoconfigure.EnableAutoConfiguration
  • 最后将工程打包,在其他项目pom文件中一如依赖就可以用了
SpringBoot的默认日志实现框架是什么?怎么切换成别的?

①SpringBoot底层也是使用slf4j+logback的方式进行日志记录,logback桥接使用logback-classic
②SpringBoot也把其他的日志都替换成了slf4j

  • log4j 适配: log4j-over-slf4j
  • jul适配:jul-to-slf4j

切换日志框架
①将 logback切换成log4j2

  • 将logback的场景启动器排除(slf4j只能运行有1个桥接器)
  • 添加log4j2的场景启动器
  • 添加log4j2的配置文件

②将 logback切换成log4j

  • 要将logback的桥接器排除
  • 添加log4j的桥接器
  • 添加log4j的配置文件

SpringBoot的启动原理?

  • 运行main方法: 初始化new SpringApplication 从spring.factories 读取 listener ApplicationContextInitializer 。
  • 运行run方法
  • 创建springApplication上下文:ServletWebServerApplicationContext
  • 预初始化上下文 : 将启动类作为配置类进行读取–>将配置注册为BeanDefinition
  • 调用refresh 加载ioc容器,包括invokeBeanFactoryPostProcessor – 解析@Import: 加载所有的自动配置类和onRefresh 创建(内置)servlet容器
  • 在这个过程中springboot会调用很多监听器对外进行扩展

SpringBoot内置Tomcat启动原理?
SpringBoot外置Tomcat启动原理?

Mybatis

MyBatis是什么?

MyBatis 是一款优秀的持久层框架,一个半 ORM(对象关系映射)框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式Java 对象)为数据库中的记录。

ORM是什么

对象关系映射,是一种为了解决关系型数据库数据与简单Java对象(POJO)的映射关系的技术。

为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里?

Hibernate属于全自动ORM映射工具,使用Hibernate查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。而Mybatis在查询关联对象或关联集合对象时,需要手动编写sql来完成,所以,称之为半自动ORM映射工具。

JDBC编程有哪些不足之处,MyBatis是如何解决这些问题的?

① 数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库连接池可解决此问题。
解决:在mybatis-config.xml中配置数据链接池,使用连接池管理数据库连接。

② Sql语句写在代码中造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。
解决:将Sql语句配置在XXXXmapper.xml文件中与java代码分离。

③ 向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数一一对应。
解决: Mybatis自动将java对象映射至sql语句。

④ 对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析比较方便。
解决:Mybatis自动将sql执行结果映射至java对象。

请说说MyBatis的工作原理

在这里插入图片描述
① 读取 MyBatis 配置文件:mybatis-config.xml 为 MyBatis 的全局配置文件,配置了 MyBatis 的运行环境等信息,例如数据库连接信息。

② 加载映射文件。

③ 构造会话工厂:通过 MyBatis 的环境等配置信息构建会话工厂SqlSessionFactory。

④ 创建会话对象:由会话工厂创建 SqlSession 对象,该对象中包含了执行 SQL语句的所有方法。

⑤ Executor 执行器:MyBatis 底层定义了一个 Executor 接口来操作数据库,它将根据 SqlSession 传递的参数动态地生成需要执行的 SQL 语句,同时负责查询缓存的维护。

⑥ MappedStatement 对象:在 Executor 接口的执行方法中有一个MappedStatement 类型的参数,该参数是对映射信息的封装,用于存储要映射的 SQL 语句的 id、参数等信息。

⑦ 输入参数映射:输入参数类型可以是 Map、List 等集合类型,也可以是基本数据类型和 POJO 类型。输入参数映射过程类似于 JDBC 对preparedStatement 对象设置参数的过程。

⑧ 输出结果映射:输出结果类型可以是 Map、 List 等集合类型,也可以是基本数据类型和 POJO 类型。输出结果映射过程类似于 JDBC 对结果集的解析过程。

#{}和${}的区别

#{} 是 占位符,对应的变量会自动加上单引号 ,能防止sql 注入
是 拼 接 符 , {} 是 拼接符, {} 对应的变量不会加上单引号,不能防止sql 注入

模糊查询like语句该怎么写

CONCAT(’%’,#{question},’%’) 使用CONCAT()函数,推荐
%${question}%’ 可能引起SQL注入,不推荐

Redis

什么是Redis

Redis是一个使用 C 语言编写的,开源的高性能非关系型的键值对数据库。键的类型只能为字符串,值支持五种数据类型:字符串、列表、集合、散列表、有序集合。

Redis有哪些优缺点

优点:

  • 读写性能优异, Redis能读的速度是110000次/s,写的速度是81000次/s。
  • 支持数据持久化,支持AOF和RDB两种持久化方式。
  • 支持事务,Redis的所有操作都是原子性的,同时Redis还支持对几个操作合并后的原子性执行。
  • 数据结构丰富,除了支持string类型的value外还支持hash、set、zset、list等数据结构。
  • 支持主从复制,主机会自动将数据同步到从机,可以进行读写分离。

缺点:

  • 数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。
  • 主机宕机,宕机前有部分数据未能及时同步到从机,切换IP后还会引入数据不一致的问题,降低了系统的可用性。
Redis为什么这么快
  • 完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。
  • 数据结构简单,对数据操作也简单,Redis 中的数据结构是专门进行设计的;
  • 采用单线程,也不存在多进程或者多线程导致的上下文切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,更没有因为可能出现死锁而导致的性能消耗;
  • 使用多路 I/O 复用模型,非阻塞 IO;
Redis有哪些数据类型

Redis主要的数据类型有5种,包括String、List、Set、Zset、Hash,满足大部分的使用要求,但是后面又增加了几种数据类型,包括bitmap、HyperLogLog和GEO。

Redis的应用场景

① 计数器
可以对 String 进行自增自减运算,从而实现计数器功能。Redis 这种内存型数据库的读写性能非常高,很适合存储频繁读写的计数量。

② 缓存
将热点数据放到内存中,设置内存的最大使用量以及淘汰策略来保证缓存的命中率。

③ 会话缓存
可以使用 Redis 来统一存储多台应用服务器的会话信息。

④ 分布式锁实现
在分布式场景下,无法使用单机环境下的锁来对多个节点上的进程进行同步。可以使用 Redis 自带的 SETNX 命令实现分布式锁,除此之外,还可以使用官方提供的 RedLock 分布式锁实现。

⑤ 根据Set无序、不可重复、支持并交差等操作的特性,可以实现统计点赞、共同关注的好友或公众号、抽奖活动等功能。

⑥ 根据ZSet不可重复且有序的特性,可以实现有序性操作,从而实现排行榜等功能。

⑦ 根据bitmap的特性,可以实现统计签到、用户登录等功能。

⑧ 根据HyperLogLog只花费 12KB 内存就可以计算接近 2^64 个元素的基数的特性,可以实现百万级网页 UV 计数。

⑨ 使用GEO的功能,可以实现类似滴滴叫车寻找附近车辆的功能。

什么是Redis持久化?

持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失。

Redis 的持久化机制是什么?各自的优缺点?

Redis 提供两种持久化机制 RDB(默认) 和 AOF 机制:

RDB 是 Redis 默认的持久化方案。在指定的时间间隔内,执行指定次数的写操作,则会将内存中的数据写入到磁盘中。即在指定目录下生成一个dump.rdb文件。Redis 重启会通过加载dump.rdb文件恢复数据。

AOF以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中命令达到恢复数据的目的,解决了数据持久化的实时性,目前是Redis持久化的主流方式。

如何选择合适的持久化方式

重启 Redis 时,我们很少使用 RDB来恢复内存状态,因为会丢失大量数据。我们通常使用 AOF 日志重放,但是重放 AOF 日志性能相对 RDB来说要慢很多,这样在 Redis 实例很大的情况下,启动需要花费很长的时间。 Redis 4.0 为了解决这个问题,带来了一个新的持久化选项——混合持久化。如果开启了混合持久化,AOF在重写时,不再是单纯将内存数据转换为RESP命令写入AOF文件,而是将重写这一刻之前的内存做RDB快照处理,并且将RDB快照内容和增量的AOF修改内存数据的命令存在一起,都写入新的AOF文件,新的文件一开始不叫appendonly.aof,等到重写完新的AOF文件才会进行改名,覆盖原有的AOF文件,完成新旧两个AOF文件的替换。

过期策略有哪些

过期策略通常有以下三种:

  • 定时过期:每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
  • 惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
  • 定期过期:每秒执行server.hz次扫描,每次扫描时间为250ms/server.hz,每次扫描从expires中抽取一定数量的key检查,如果过期key大于四分之一,则下次扫描继续扫描此expires,否则扫描下一个expires。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。
逐出策略有哪些

检测易失数据(可能会过期的数据集server.db[i].expires)
①volatire-lru:挑选最近最长时间未被使用的数据淘汰
②volatile-lfu:挑选最近使用次数最少的数据淘汰
③volatile-ttl:挑选将要过期的数据淘汰
④volatile-random:任意选择数据淘汰
检测全库数据(所有数据集server.db[i].dict)
⑤allkeys-lru:挑选最近最长时间未被使用的数据淘汰
⑥allkeys-lfu:挑选最近使用次数最少的数据淘汰
⑦allkeys-random:任意选择数据淘汰
放弃数据驱逐
⑧no-enviction:禁止驱逐数据(redis4.0中默认策略),会引发错误OOM

Redis事务是什么?

Redis事务就是一个命令执行的队列,将一系列预定义命令包装成一个整体(队列),当执行时,一次性按照添加的顺序依次执行,中间不会被打断或者干扰,具有一致性和隔离性。

Redis如何实现分布式锁

Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系Redis中可以使用SETNX命令实现分布式锁。使用SETNX命令获取锁,若返回0(key已存在,锁已存在)则获取失败,反之获取成功。
在这里插入图片描述

注意:为了防止获取锁后程序出现异常,导致其他线程/进程调用SETNX命令总是返回0而进入死锁状态,需要为该key设置一个“合理”的过期时间。

缓存雪崩

缓存雪崩是指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。

解决方案:

  • 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
  • 一般并发量不是特别多的时候,使用最多的解决方案是加锁排队。
缓存穿透

缓存穿透是指缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。

解决方案:

  • 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。
  • 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力
缓存击穿

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

解决方案:

  • 设置热点数据永远不过期。
  • 加互斥锁,互斥锁
缓存预热

缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!

解决方案:

  • 直接写个缓存刷新页面,上线时手工操作一下;
  • 数据量不大,可以在项目启动的时候自动进行加载;
  • 定时刷新缓存;
如何保证缓存与数据库双写时的数据一致性?

https://blog.csdn.net/weixin_47184173/article/details/117652925


假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如果将它们全部找出来?

使用keys指令可以扫出指定模式的key列表。
对方接着追问:如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题?
这个时候你要回答redis关键的一个特性:redis的单线程的。keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。

使用Redis做过异步队列吗,是如何实现的

使用list类型保存数据信息,rpush生产消息,lpop消费消息,当lpop没有消息时,可以sleep一段时间,然后再检查有没有信息,如果不想sleep的话,可以使用blpop, 在没有信息的时候,会一直阻塞,直到信息的到来。redis可以通过pub/sub主题订阅模式实现一个生产者,多个消费者,当然也存在一定的缺点,当消费者下线时,生产的消息会丢失。

Jedis与Redisson对比有什么优缺点?

Jedis是Redis的Java实现的客户端,其API提供了比较全面的Redis命令的支持;Redisson实现了分布式和可扩展的Java数据结构,和Jedis相比,功能较为简单,不支持字符串操作,不支持排序、事务、管道、分区等Redis特性。Redisson的宗旨是促进使用者对Redis的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。

为什么要用 Redis 而不用 map/guava 做缓存?

缓存分为本地缓存和分布式缓存。以 Java 为例,使用自带的 map 或者 guava实现的是本地缓存,最主要的特点是轻量以及快速,生命周期随着 jvm 的销毁而结束,并且在多实例的情况下,每个实例都需要各自保存一份缓存,缓存不具有一致性。

使用 redis 或 memcached 之类的称为分布式缓存,在多实例的情况下,各实例共用一份缓存数据,缓存具有一致性。缺点是需要保持 redis 或 memcached服务的高可用,整个程序架构上较为复杂。

Redis如何做内存优化?

可以好好利用Hash,list,sorted set,set等集合类型数据,因为通常情况下很多小的Key-Value可以用更紧凑的方式存放到一起。尽可能使用散列表(hash),散列表(是说散列表里面存储的数少)使用的内存非常小,所以你应该尽可能的将你的数据模型抽象到一个散列表里面。比如你的web系统中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的key,而是应该把这个用户的所有信息存储到一张散列表里面。

MQ

为什么使用MQ,有什么优缺点

优点:
①应用解耦:提高系统容错性和可维护性
不使用MQ时,系统间的消息通信需要通过远程调用接口,这样会导致需要添加或者减少系统通信,就需要修改对应的代码,如果使用MQ,系统间的消息通信不需要调用接口,只需要将消息发送到MQ中,需要接受消息的系统自己去MQ中获取即可,从而实现了系统间的解耦。

②异步提速:提升用户体验和系统吞吐量
使用MQ可以将需要调用其他系统执行完成的操作交由其他系统自己去MQ取数据异步完成,从而节省执行时间。

③削峰填谷:提高系统稳定性
一般的MySQL,能够抗住的QPS有限,如果并发量超过了可以承受的范围,可能Mysql就挂了,导致系统崩溃。使用MQ可以将请求数据存放到MQ中,由系统逐步进行消费,保证系统有序进行。

缺点

  • 系统可用性降低:系统引入的外部依赖越多,系统稳定性越差。一旦 MQ 宕机,就会对业务造成影响。
  • 系统复杂度提高:以前系统间是同步的远程调用,现在是通过 MQ 进行异步调用,需要考虑消息的重复消费、丢失以及系统一致性等问题
你们公司生产环境用的是什么消息中间件?

说RabbitMQ,他的好处在于可以支撑高并发、高吞吐、性能很高,同时有非常完善便捷的后台管理界面可以使用。另外,他还支持集群化、高可用部署架构、消息高可靠支持,功能较为完善。而且经过调研,国内各大互联网公司落地大规模RabbitMQ集群支撑自身业务的case较多,国内各种中小型互联网公司使用RabbitMQ的实践也比较多。除此之外,RabbitMQ的开源社区很活跃,较高频率的迭代版本,来修复发现的bug以及进行各种优化,因此综合考虑过后,公司采取了RabbitMQ。

RabbitMQ

什么是RabbitMQ?

RabbitMQ是一款开源的,Erlang编写的,基于AMQP协议的消息中间件,主要用来做系统间的应用解耦、异步提速和削峰填谷。

RabbitMQ基本概念

在这里插入图片描述

  • Broker:接收和分发消息的应用,RabbitMQ Server就是 Message Broker
  • Virtual host:vhost 可以理解为虚拟 broker ,即 mini-RabbitMQ server。其内部均含有独立的queue、exchange 和 binding 等,但最最重要的是,其拥有独立的权限系统,可以做到vhost 范围的用户控制。当然,从 RabbitMQ 的全局角度,vhost 可以作为不同权限隔离的手段(一个典型的例子就是不同的应用可以跑在不同的 vhost 中)
  • Connection:publisher/consumer 和 broker 之间的 TCP 连接
  • 如果每一次访问 RabbitMQ 都建立一个 Connection,在消息量大的时候建立 TCP Connection的开销将是巨大的,效率也较低。Channel 是在 connection 内部建立的逻辑连接,如果应用程序支持多线程,通常每个thread创建单独的 channel 进行通讯,AMQP method 包含了channel id 帮助客户端和message broker 识别 channel,所以 channel 之间是完全隔离的。Channel 作为轻量级的 Connection 极大减少了操作系统建立 TCP connection 的开销
  • Exchange:message 到达 broker 的第一站,根据分发规则,匹配查询表中的 routing key,分发消息到queue 中去。常用的类型有:direct (point-to-point), topic (publish-subscribe) and fanout (multicast)
  • Queue:消息最终被送到这里等待 consumer 取走
  • Binding:exchange 和 queue 之间的虚拟连接,binding 中可以包含 routing key。Binding 信息被保存到 exchange 中的查询表中,用于 message 的分发依据
RabbitMQ的工作模式
HelloWorld(简单模式)

在这里插入图片描述
一个生产者、一个消费者,不需要设置交换机(使用默认的交换机)

work queues(工作队列模式)

在这里插入图片描述
一个生产者、多个消费者(竞争关系),不需要设置交换机(使用默认的交换机)

Publish/Subscribe(发布与订阅模式)

在这里插入图片描述
设置类型为 fanout 的交换机,并且交换机和队列进行绑定,当发送消息到交换机后,交换机会将消息发送到绑定的队列

Routing(路由模式)

在这里插入图片描述
设置类型为 direct 的交换机,交换机和队列进行绑定,并且指定 routing key,当发送消息到交换机后,交换机会根据 routing key 将消息发送到对应的队列

Topics(主题模式)

在这里插入图片描述
设置类型为 topic 的交换机,交换机和队列进行绑定,并且指定通配符方式的 routing key,当发送消息到交换机后,交换机会根据 routing key 将消息发送到对应的队列

消息基于什么传输?

由于 TCP 连接的创建和销毁开销较大,且并发数受系统资源限制,会造成性能瓶颈。RabbitMQ 使用信道的方式来传输数据。信道是建立在真实的 TCP 连接内的虚拟连接,且每条 TCP 连接上的信道数量没有限制。

消息如何分发?

若该队列至少有一个消费者订阅,消息默认以轮训(round-robin)的方式发送给消费者,每条消息只会分发给一个订阅的消费者(前提是消费者能够正常处理消息并进行确认),通过路由可实现多消费的功能。

消息怎么路由?

消息提供方->路由->一至多个队列。消息发布到交换器时,消息将拥有一个路由键(routing key),在消息创建时设定。通过队列路由键,可以把队列绑定到交换器上。消息到达交换器后,RabbitMQ 会将消息的路由键与队列的路由键进行匹配(针对不同的交换器有不同的路由规则),常用的交换器主要分为一下三种:

  • fanout:如果交换器收到消息 ,将会广播到所有绑定的队列上
  • direct:如果路由键完全匹配,消息就被投递到相应的队列
  • topic:如果路由键跟通配符匹配,消息就被投递到相应的队列
MQ 有哪些常见问题?如何解决这些问题?

① 消息的顺序问题:消息有序指的是可以按照消息的发送顺序来消费,可以通过保证生产者 - MQServer - 消费者是一对一对一的关系来解决这个问题

② 消息的重复问题:消息重复问题主要是因为发送者与服务器之前的网络问题导致消息多次发送,因此要解决这个问题,实际上就是解决消息幂等性问题。幂等性指一次和多次请求某一个资源,对于资源本身应该具有同样的结果。在MQ中指,消费多条相同的消息,得到与消费该消息一次相同的结果。

解决方案:全局唯一ID + Redis

生产者在发送消息的时候,为每条消息设置一个全局唯一的messageId,消费者拿到消息后使用setnx命令,将messageId作为key放到redis中:setnx(messageId,1),若返回1,说明之前没有消费过,正常消费;若返回0,说明这条消息之前已消费过,抛弃。
PS: setnx命令的作用是若给定的key不存在,执行set操作,返回1,若给定的Key已存在,不做任何操作,返回0

③ 如何实现消息的可靠性投递
https://zhuanlan.zhihu.com/p/225839966

④ 如何解决消息积压问题?

其实本质针对的场景,都是说,可能你的消费端出了问题,不消费了;或者消费的速度极其慢,造成消息堆积了,MQ存储快要爆了,甚至开始过期失效删除数据了。针对这个问题可以有事前、事中、事后三种处理:

事前:开发预警程序,监控最大的可堆积消息数,超过就发预警消息(比如短信),不要等出生产事故了再处理。

事中:看看消费端是不是故障停止了,紧急重启。

事后:需要对消费端紧急扩容 ,增加处理消费者进程,如扩充10倍处理,但其实这也有个问题,即数据库的吞吐是有限制的,如果是消费到数据库也是没办法巨量扩容的,所以还是要在吞吐能力支持下老老实实的泄洪消费。或者先将消息队列中的数据存储到数据库中,后面再慢慢分析消费。

死信队列是什么,有什么用?

死信队列,英文缩写:DLX 。Dead Letter Exchange(死信交换机),当消息成为Dead message后,可以被重新发送到另一个交换机,这个交换机就是DLX,交换机可以将消息路由到对应的队列,可用于排查 message 被 reject 或 undeliver 的原因。
在这里插入图片描述
成为死信队列的三种情况:

  • 队列消息长度到达限制
  • 消费者拒接消费消息,basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false
  • 原队列存在消息过期设置,消息到达超时时间未被消费

向不存在的 exchange 发 publish 消息会发生什么?向不存在的 queue 执行consume 动作会发生什么?

都会收到 Channel.Close 信令告之不存在(内含原因 404 NOT_FOUND)。

routing_key 和 binding_key 的最大长度是多少?

255 字节。

RabbitMQ 上的一个 queue 中存放的 message 是否有数量限制?

可以在声明队列时进行参数限定,如果不限定可以认为是无限制,因为限制取决于机器的内存,但是消息过多会导致处理效率的下降。

RabbitMQ 允许发送的 message 最大可达多大?

在版本3.8开始是512M,但是消息大小最多不要超过4M,因为,当包大小达到4.5M时,服务器的性能出现明显的异常,传输率尤其是每秒订阅消息的数量,出现波动,不稳定;

RocketMQ

ES

什么是倒排索引,为什么ES搜索比较快?

倒排索引是一种根据一定规则组成的数据结构,方便于数据查询

创建倒排索引是对正向索引的一种特殊处理,流程如下:

  • 将每一个文档的数据利用算法分词,得到一个个词条
  • 创建表,每行数据包括词条、词条所在文档id、位置等信息
  • 因为词条唯一性,可以给词条创建索引,例如hash表结构索引

倒排索引的搜索流程如下:

  • 将搜索内容进行分词,得到词条
  • 再根据词条到倒排索引中查询得到相关文档id地址等信息
  • 拿着文档id等信息到正向索引中查询具体文档

虽然要先查询倒排索引,再查询倒排索引,但是无论是词条、还是文档id都建立了索引,查询速度非常快!无需全表扫描。

ES的分层是怎样的

ES大体上分为索引层、文档层、field层,分别对应Mysql数据库中的表、数据行、列,而定义索引的字段属性可以通过mapping进行定义,相当于Mysql中的表结构

将Mysql的表导入到ES时,如何考虑哪些字段需要分词加倒排索引

这个需要根据业务的实际情况做分析,可以先根据Mysql表的字段进行设定最常用的查询语句,判断哪些字段是经常需要放在where中进行查询的,一般放在where中进行查询的字段比较适合进行分词添加倒排索引,比如商品表的商品名称。

Dubbo

Dubbo 是什么?

Dubbo 是一款高性能、轻量级的开源 RPC 框架,提供服务自动注册、自动发现等高效服务治理方案,可以和 Spring 框架无缝集成。

Dubbo 的主要功能有哪些?
  • 透明化的远程方法调用:就像调用本地方法一样调用远程方法,只需简单配置, 没有任何API侵入。
  • 软负载均衡
  • 服务自动注册与发现:不再需要写死服务提供方地址,注册中心基于接口名查询服务提供者的IP地址,并且能够平滑添加或删除服务提供者。

微服务

基础概念

什么是微服务架构,有什么优缺点?

微服务是一种将单个应用程序开发为一组小服务的方法,每个服务都在自己的进程中运行,并通过轻量级机制(通常是HTTP资源API)进行服务之间交互。每个服务都是根据业务功能对系统做拆分,每个业务功能模块作为独立项目开发。

优点:

  • 每个微服务都很小,这样能聚焦一个指定的业务功能或业务需求;
  • 微服务易于被开发人员理解,修改和维护,因此能够被小团队单独开发;
  • 微服务是松耦合的,无论是在开发阶段或部署阶段都是独立的;
  • 微服务能使用不同的语言开发;

缺点:

  • 运维要求较高;
  • 分布式的复杂性;
  • 接口调整成本高;
微服务的架构特征
  • 单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责
  • 自治:团队独立、技术独立、数据独立,独立部署和交付
  • 面向服务:服务提供统一标准的接口,与语言和技术无关
  • 隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题
微服务架构相对于单体架构需要解决什么问题

服务注册发现、统一配置管理、

服务拆分原则
  • 不同微服务,不要重复开发相同业务
  • 微服务数据独立,不要访问其它微服务的数据库
  • 微服务可以将自己的业务暴露为接口,供其它微服务调用

Eureka

Eureka的工作流程?

在这里插入图片描述

  • user-service服务实例启动后,将自己的信息注册到eureka-server(Eureka服务端),这个叫服务注册
  • eureka-server保存服务名称到服务实例地址列表的映射关系
  • order-service根据服务名称,拉取实例地址列表,这个叫服务发现或服务拉取
  • order-service从实例列表中利用负载均衡算法选中一个实例地址,然后向该实例地址发起远程调用
  • 在此期间,user-service会每隔一段时间(默认30秒)向eureka-server发起请求,报告自己状态,称为心跳
  • order-service拉取服务时,就能将故障实例排除了

Nacos

Nacos与Eureka的区别

在这里插入图片描述
共同点:

  • 都支持服务注册和服务拉取
  • 都支持服务提供者心跳方式做健康检测

区别:

  • Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
  • 临时实例心跳不正常会被剔除,非临时实例则不会被剔除
  • Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
  • Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式,而Eureka采用AP方式
Nacos如何实现从微服务拉取配置

在这里插入图片描述
添加bootstrap.yml文件,并配置Nacos的对应信息:

spring:
  application:
    name: userservice # 服务名称
  profiles:
    active: dev #开发环境,这里是dev 
  cloud:
    nacos:
      server-addr: localhost:8848 # Nacos地址
      config:
        file-extension: yaml # 文件后缀名
如何配置热更新

① 在@Value注入的变量所在类上添加注解@RefreshScope:
在这里插入图片描述
② 使用@ConfigurationProperties注解代替@Value注解

配置共享的优先级

在这里插入图片描述

Gateway

过滤器执行顺序

在这里插入图片描述

  • 每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前。
  • GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,由我们自己指定
  • 路由过滤器和defaultFilter的order由Spring指定,默认是按照声明顺序从1递增。
  • 当过滤器的order值一样时,会按照 defaultFilter > 路由过滤器 > GlobalFilter的顺序执行。

Rinbon

Rinbon如何开启饥饿加载
ribbon:
  eager-load:
    enabled: true
    clients: userservice
Rinbon的负载均衡策略有哪些

随机策略、轮训策略、最低并发策略等

Feign

Feign远程调用的流程是怎样的?
  • 拦截我们的Feign请求http://userservice/user/1
  • RibbonLoadBalancerClient会从请求url中获取服务名称,比如xxx-service
  • DynamicServerListLoadBalancer根据user-service到eureka拉取服务列表
  • eureka返回列表,localhost:8081、localhost:8082
  • IRule利用内置负载均衡规则,从列表中选择一个,例如localhost:8081
  • RibbonLoadBalancerClient修改请求地址,用localhost:8081替代userservice,得到http://localhost:8081/user/1,发起真实请求

Sentinel

服务雪崩有哪几种解决方案

① 超时处理:设定超时时间,请求超过一定时间没有响应就返回错误信息,不会无休止等待
② 仓壁模式:限定每个业务能使用的线程数,避免耗尽整个tomcat的资源,因此也叫线程隔离
③ 断路器模式:由断路器统计业务执行的异常比例,如果超出阈值则会熔断该业务,拦截访问该业务的一切请求。
④ 流量控制:限制业务访问的QPS,避免服务因流量的突增而故障。

规则管理模式有哪些?

① 原始模式:Sentinel的默认模式,将规则保存在内存,重启服务会丢失。

② pull模式:控制台将配置的规则推送到Sentinel客户端,而客户端会将配置规则保存在本地文件或数据库中。以后会定时去本地文件或数据库中查询,更新本地规则。
在这里插入图片描述
③ push模式:控制台将配置规则推送到远程配置中心,例如Nacos。Sentinel客户端监听Nacos,获取配置变更的推送消息,完成本地配置更新。
在这里插入图片描述

Seata

CAP定理
  • Consistency(一致性)
  • Availability(可用性)
  • Partition tolerance (分区容错性)

在分布式系统中,系统间的网络不能100%保证健康,一定会有故障的时候,而服务有必须对外保证服务。因此Partition Tolerance不可避免。当节点接收到新的数据变更时,就会出现问题了:
如果此时要保证一致性,就必须等待网络恢复,完成数据同步后,整个集群才对外提供服务,服务处于阻塞状态,不可用。如果此时要保证可用性,就不能等待网络恢复,那node01、node02与node03之间就会出现数据不一致。也就是说,在P一定会出现的情况下,A和C之间只能实现一个。

因此,解决分布式问题,一般有两种思路:

  • AP模式:各子事务分别执行和提交,允许出现结果不一致,然后采用弥补措施恢复数据即可,实现最终一致。
  • CP模式:各个子事务执行后互相等待,同时提交,同时回滚,达成强一致。但事务等待过程中,处于弱可用状态。
BASE理论

BASE理论是对CAP的一种解决思路,包含三个思想:

  • Basically Available (基本可用):分布式系统在出现故障时,允许损失部分可用性,即保证核心可用。
  • Soft State(软状态):在一定时间内,允许出现中间状态,比如临时的不一致状态。
  • Eventually Consistent(最终一致性):虽然无法保证强一致性,但是在软状态结束后,最终达到数据一致。
Seata的三个核心概念

在这里插入图片描述

  • TC(事务协调者):维护全局和分支事务的状态,协调全局事务的提交和回滚
  • TM(事务管理者):定义全局事务范围,开始、提交或者回滚全局事务
  • RM(资源管理器):与TC交谈,注册分支事务和报告分支事务状态,并驱动分支事务的提交和回滚
Seata提供的四种分布式事务解决方案
  • XA模式:强一致性分阶段事务模式,牺牲了一定的可用性,无业务侵入
  • TCC模式:最终一致的分阶段事务模式,有业务侵入
  • AT模式:最终一致的分阶段事务模式,无业务侵入,也是Seata的默认模式
  • SAGA模式:长事务模式,有业务侵入

① XA模型:强一致性分阶段事务模式,牺牲了一定的可用性,无业务侵入
在这里插入图片描述
优点:

  • 事务的强一致性,满足ACID原则。
  • 常用数据库都支持,实现简单,并且没有代码侵入

缺点:

  • 因为一阶段需要锁定数据库资源,等待二阶段结束才释放,性能较差
  • 依赖关系型数据库实现事务

② AT模式:最终一致的分阶段事务模式,有业务侵入

在这里插入图片描述
脏写问题:
在这里插入图片描述
优点:

  • 一阶段完成直接提交事务,释放数据库资源,性能比较好
  • 利用全局锁实现读写隔离
  • 没有代码侵入,框架自动完成回滚和提交

缺点:

  • 两阶段之间属于软状态,属于最终一致
  • 框架的快照功能会影响性能,但比XA模式要好很多

③ TCC模式

在这里插入图片描述

TCC模式与AT模式非常相似,每阶段都是独立事务,不同的是TCC通过人工编码来实现数据恢复。需要实现三个方法:

  • Try:资源的检测和预留;
  • Confirm:完成资源操作业务;要求 Try 成功 Confirm 一定要能成功。
  • Cancel:预留资源释放,可以理解为try的反向操作。

问题:
在这里插入图片描述
优点:

  • 一阶段完成直接提交事务,释放数据库资源,性能好
  • 相比AT模型,无需生成快照,无需使用全局锁,性能最强
  • 不依赖数据库事务,而是依赖补偿操作,可以用于非事务型数据库

缺点:

  • 有代码侵入,需要人为编写try、Confirm和Cancel接口,太麻烦
  • 软状态,事务是最终一致
  • 需要考虑Confirm和Cancel的失败情况,做好幂等处理

④ SAGA模式

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/705036.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

地面自动气象站的工作原理

TH-CQX10地面自动气象站是一种部署在地面上&#xff0c;用于监测和记录气象要素的自动化设备。能够自动观测、储存和发送气象数据的设备。它能够实时监测和记录温度、湿度、风速、风向、气压等多种气象参数&#xff0c;为气象监测、气候研究、农业生产、环境监测等领域提供准确…

C++中的结构体——结构体案例1_2

案例描述 学校正在做毕设项目&#xff0c;每位老师指导5名学生&#xff0c;总共有3名老师&#xff0c;需求如下 设计学生和老师的结构体&#xff0c;其中在老师的结构体中&#xff0c;有老师的姓名和一个存放5名学生的数组作为成员 学生的成员有姓名、考试分数&#xff0c;创…

Python写UI自动化--playwright(安装)

Playwright是微软推出的开源自动化测试工具&#xff0c;专为跨浏览器端到端测试设计。Playwright可以在多个浏览器引擎上运行测试&#xff0c;包括基于Chromium的浏览器&#xff08;如Chrome、Edge&#xff09;、Firefox和WebKit&#xff08;Safari的技术基础&#xff09;。支持…

信息收集---网站目录和CMS指纹识别

一. 网站目录收集 1. 常见网站敏感文件 网站的备份文件/数据库备份文件 wwwroot.zip Db.zip 后台登陆的目录 manage login 安装包&#xff08;源码&#xff09; 上传的目录uploads mysql的管理界面 phpmyadmin 程序的安装路径 2. Dirb 工具 工具介绍 dirb 是一款用…

【Spine学习06】之IK约束绑定,制作人物待机动画,图表塞贝尔曲线优化动作

引入IK约束的概念&#xff1a; 约束目标父级 被约束骨骼子集 这样理解更好&#xff0c;约束目标可以控制被约束的两个骨骼运作 IK约束绑定过程中呢&#xff0c;如果直接绑定最下面的脚掌骨骼会发生偏移&#xff0c;所以在开始处理IK之前&#xff0c;需要先设置一个ROOT结点下的…

自定义类型:枚举(enum)+联合体(union)

枚举联合体 一.枚举1.枚举类型的声明2.枚举类型的优点3.枚举类型的使用 二.联合体1.联合体类型的声明2.联合体的特点3.相同成员的结构体和联合体对比4.联合体大小的计算5.联合体的练习&#xff08;判断大小端&#xff09;6.联合体节省空间例题 一.枚举 1.枚举类型的声明 枚举…

HTML制作一个日蚀的动画特效

大家好&#xff0c;今天制作一个日蚀动画特效&#xff01; 先看具体效果&#xff1a; 使用一个逐渐扩大的圆形阴影来模拟月亮遮挡太阳的效果。使用了CSS的keyframes动画和border-radius属性来创建一个简单的圆形阴影效果。 HTML <!DOCTYPE html> <html lang"e…

Linux构建本地时间同步ntp

环境介绍&#xff1a; 主机名 IP地址 系统发行版 环境 Node01 192.168.100.102 Centos 7.4 可联网、已关闭防火墙selinux Node02 192.168.100.103 Centos 7.4 已关闭防火墙selinux 1.主节点同步阿里云标准时间 在保证连接外网的情况下&#xff0c;同步阿里服务器的…

Rust 实战丨SSE(Server-Sent Events)

&#x1f4cc; SSE&#xff08;Server-Sent Events&#xff09;是一种允许服务器向客户端浏览器推送信息的技术。它是 HTML5 的一部分&#xff0c;专门用于建立一个单向的从服务器到客户端的通信连接。SSE的使用场景非常广泛&#xff0c;包括实时消息推送、实时通知更新等。 S…

Linux2(文件类型分类 基本命令2 重定向)

目录 一、文件类型分类 二、基本命令2 1. find 帮助查询 2. stat 查看文件的信息 3. wc 统计文本 4. 查看文本内容 4.1 cat 4.2 more 4.3 less 4.4 head 4.5 tail 5. cal 显示日历 6. date 显示时间 7. du 文件大小 8. ln 链接 软链接 硬链接 区别 9. histo…

如何解除内存卡的写保护并格式化为exFAT文件系统

最近有客户提问内存卡提示写保护&#xff0c;且无法格式化为exFAT格式的问题&#xff0c;可能是由于多种原因引起的。以下是一些可能的解决方法&#xff1a; 1. 检查物理写保护开关 一些SD卡和MicroSD卡适配器上有一个小的物理开关&#xff0c;可以启用或禁用写保护。确保这个…

在Dataworks调度里检查上游表的分区是否已经产出

在Dataworks调度里检查上游表的分区是否已经产出 新建PyOdps3节点&#xff0c;贴如如下代码&#xff1a; import sys import time from datetime import datetimebizdate args[bizdate] if not o.exist_table(args[table]):sys.exit(1)# 设置结束时间为今天的20:00 end_time …

[项目推荐]EmoLLM-心理健康大模型

EmoLLM 是一系列能够支持理解用户-支持用户-帮助用户心理健康辅导链路的开源心理健康大模型&#xff0c;由LLM指令微调而来。它旨在全面理解和促进个体、群体乃至整个社会的心理健康状态。 项目介绍 GitHub&#xff1a;https://github.com/SmartFlowAI/EmoLLM 【EmoLLM项目提供…

30、 shell脚本进阶

shell脚本ifcase 一、条件测试 1.1、条件测试&#xff1a;$?----返回码&#xff0c;判断命令或者脚本是否执行成功&#xff08;最近的一条&#xff09; 0 true 为真就是成功 成立 非0 false 失败或者异常。 二、test命令 test命令&#xff1a;可以进行条件测试&#xff…

【SQL边干边学系列】07高级问题-3

文章目录 前言回顾高级问题41.逾期订单42.逾期订单-哪些员工&#xff1f;43.逾期订单与总订单相比44.逾期订单与总订单相比 - 丢失的员工45.逾期订单与总订单相比 - 修复null46.逾期订单与总订单之间的百分比47.逾期订单与总订单相比 - 修正decimal 答案41.逾期订单42.逾期订单…

Python Flask框架基础(七)留言板

本章示例程序是一个非常简单的留言板程序SayHello&#xff0c;涉及的知识完全是前面六个章节的内容 。这一章会基于这个程序介绍一种组织代码的形式&#xff0c;并了解Web程序开发流程&#xff0c;对前面六章的知识进行简单的回顾复习。 在具体的开发中&#xff0c;代码编写主…

AI大模型探索之路-实战篇:智能化IT领域搜索引擎之GLM-4大模型技术的实践探索

系列篇章&#x1f4a5; No.文章1AI大模型探索之路-实战篇&#xff1a;智能化IT领域搜索引擎的构建与初步实践2AI大模型探索之路-实战篇&#xff1a;智能化IT领域搜索引擎之GLM-4大模型技术的实践探索3AI大模型探索之路-实战篇&#xff1a;智能化IT领域搜索引擎之知乎网站数据获…

【计算机毕业设计】257基于大学生社团活动管理的微信小程序

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

二分+ST表+递推,Cf 1237D - Balanced Playlist

一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 Problem - 1237D - Codeforces 二、解题报告 1、思路分析 case3提示我们一件事情&#xff1a;如果存在某个位置永远不停止&#xff0c;那么所有位置都满足永远不停止 很容易证明 随着下标右移&#xff0c…

YOLOv10原理与实战训练自己的数据集

课程链接&#xff1a;YOLOv10原理与实战训练自己的数据集_在线视频教程-CSDN程序员研修院 YOLOv10是最近提出的YOLO的改进版本。在后处理方面&#xff0c;提出了一致性双重分配策略用于无NMS训练&#xff0c;从而实现了高效的端到端检测。在模型架构方面&#xff0c;引入了全面…