java面试必备100题-javase基础篇

JAVA面试100****道

数组和链表的区别:

从逻辑结构上来看,数组必须实现定于固定的长度,不能适应数据动态增减的情况,即数组的大小一旦 定义就不能改变。当数据增加是,可能超过原先定义的元素的个数;当数据减少时,造成内存浪费;链 表动态进行存储分配,可以适应数据动态地增减的情况,且可以方便地插入、删除数据项。

从内存存储的角度看;数组从栈中分配空间(用new则在堆上创建),对程序员方便快速,但是自由度小;链表从堆中分配空间,自由度大但是申请管理比较麻烦。

从访问方式类看,数组在内存中是连续的存储,因此可以利用下标索引进行访问;链表是链式存储结 构,在访问元素时候只能够通过线性方式由前到后顺序的访问,所以访问效率比数组要低。

Java四种引用

\1. 强引用:开发中用的最多的就是强引用了。只要某个对象与强引用关联,那么JVM在内存不足的情况下,宁愿抛出outOfMemoryError错误,也不会回收此类对象。

\2. 弱引用:java中使用WeakReference来表示弱引用。如果某个对象与弱引用关联,那么当JVM在进 行垃圾回收时,无论内存是否充足,都会回收此类对象。

\3. 软引用:java中使用SoftRefence来表示软引用,如果某个对象与软引用关联,那么JVM只会在内存 不足的情况下回收该对象。

那么利用这个特性,软引用可以用来做缓存。

\4. 虚引用:java中使用PhantomReference来表示虚引用。虚引用,虚引用,引用就像形同虚设一 样,就像某个对象没有引用与之关联一样。若某个对象与虚引用关联,那么在任何时候都可能被

JVM回收掉。虚引用不能单独使用,必须配合引用队列一起使用。

JAVA反射

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

\2. 什么地方用到反射:JDBC中,利用反射动态加载了数据库驱动程序,Web服务器中利用反射调用了Servlet的服务方法

JAVA多线程实现方式

继承Thread类,重写run函数;实现Runnable接口,重写run函数;实现Callable接口,重写call函数。

JVM的分区

方法区:

\1. 有时候也成为永久代,在该区内很少发生垃圾回收,但是并不代表不发生GC,在这里进行的GC主要是对方法区里的常量池和对类型的卸载

\2. 方法区主要用来存储已被虚拟机加载的类的信息、常量、静态变量和即时编译器编译后的代码等数 据。 该区域是被线程共享的。

\3. 方法区里有一个运行时常量池,用于存放静态编译产生的字面量和符号引用。该常量池具有动态 性,也就是说常量并不一定是编译时确定,运行时生成的常量也会存在这个常量池中。

虚拟机栈:

\1. 虚拟机栈也就是我们平常所称的栈内存,它为java方法服务,每个方法在执行的时候都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接和方法出口等信息。

\2. 虚拟机栈是线程私有的,它的生命周期与线程相同。

\3. 局部变量表里存储的是基本数据类型、returnAddress类型(指向一条字节码指令的地址)和对象引用,这个对象引用有可能是指向对象起始地址的一个指针,也有可能是代表对象的句柄或者与对 象相关联的位置。

\4. 局部变量所需的内存空间在编译器间确定操作数栈的作用主要用来存储运算结果以及运算的操作 数,它不同于局部变量表通过索引来访问,而是压栈和出栈的方式每个栈帧都包含一个指向运行时 常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接.动态链接就 是将常量池中的符号引用在运行期转化为直接引用。

本地方法栈

本地方法栈和虚拟机栈类似,只不过本地方法栈为Native方法服务。

java堆是所有线程所共享的一块内存,在虚拟机启动时创建,几乎所有的对象实例都在这里创建,因此该区域经常发生垃圾回收操作。

程序计数器

内存空间小,字节码解释器工作时通过改变这个计数值可以选取下一条需要执行的字节码指令,分支、 循环、跳转、异常处理和线程恢复等功能都需要依赖这个计数器完成。该内存区域是唯一一个java虚拟机规范没有规定任何OOM情况的区域。

JAVA中堆和栈的区别

区别:1、栈内存存储的是局部变量,而堆内存存储的是实体;

2、栈内存的更新速度要快于堆内存,因为局部变量的生命周期很短;

3、栈内存存放的变量生命周期一旦结束就会被释放,而堆内存存放的实体会被垃圾回收机制不 定时的回收。

JAVA八大数据类型

byte:8位,最大存储数据量是255,存放的数据范围是-128~127之间。

]short:16位,最大数据存储量是65536,数据范围是-32768~32767之间。 int:32位,最大数据存储容量是2的32次方减1,数据范围是负的2的31次方到正的2的31次方减1。

long:64位,最大数据存储容量是2的64次方减1,数据范围为负的2的63次方到正的2的63次方减1。

float:32位,数据范围在3.4e-45~1.4e38,直接赋值时必须在数字后加上f或F。

double:64位,数据范围在4.9e-324~1.8e308,赋值时可以加d或D也可以不加。

boolean:只有true和false两个取值。

char:16位,存储Unicode码,用单引号赋值。

JAVA常用设计模式以及应用场景

1、单例模式:保证一个类仅有一个实例并提供一个全局访问点,如一些配置文件或者管理类可以设计为 单例,常用的线程池也是单例。

2、模板方法:在定义好的算法骨架下允许子类为一个或多个步骤提供实现,一次性实现算法的不变部分 将可变部分留给子类实现,当子类实现代码逻辑雷同时可以使用此设计模式。

3、工厂模式:创建对象需要大量的重复代码时,通过子类实现方法来创建对象。如Spring中通过工厂模式将创建对象的任务交给容器管理。

4、原型模式 :在应用程序可能有某些对象的结构比较复杂,但又需要频繁的使用它们,如这个时候不断的新建这个对象势必会大大损耗系统内存的,这个时候需要使用原型模式来对这个结构复杂又要频繁 使用的对象进行克隆。所以原型模式就是用原型实例指定创建对象的种类,且通过复制这些原型创建新 的对象。主要应用与那些创建新对象的成本过大时。它的主要优点就是简化了新对象的创建过程,提高 了效率,同时原型模式提供了简化的创建结构。

5、建造者模式:讲复杂对象的构建和表示分离,适用于流程固定,但是顺序不一定固定的场景。如需要 给一个对象多次给不同的属性赋值,可以使用链式调用传参,最后生成对象。如策略模式,观察者模

式,模板方法模式,foreach中的迭代器模式,spring 中ASM的访问者模式,动态代理等都有一些了解。

6、适配器模式:在应用程序中可能需要将两个不同接口的类来进行通信,在不修改这两个的前提下可能 会需要某个中间件来完成这个衔接的过程。这个中间件就是适配器。所谓适配器模式就是将一个类的接 口,转换成客户期望的另一个接口。它可以让原本两个不兼容的接口能够无缝完成对接。作为中间件的 适配器将目标类和适配者解耦,增加了类的透明性和可复用性。

JDK、JRE、JVM

JDK:

Java Develpment Kit java 开发工具

JRE:

Java Runtime Environment java运行时环境JVM:

java Virtual Machine java 虚拟机

== 和 equals比较

==:对比的是栈中的值,基本数据类型比较的是变量值,引用类型比较堆中内存对象的地址

equals()是Object提供的一个方法.Object中equals()方法的默认实现就是返回两个对象==的比较结果.但是equals()可以被重写

hashCode 与 equals

hashCode介绍:

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

hashCode()函数。

散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到 了散列码!(可以快速找到所需要的对象)

为什么要有hashCode:

以“HashSet如何检查重复”为例子来说明为什么要有hashCode:

对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,看该位置是否有 值,如果没有、HashSet会假设对象没有重复出现。但是如果发现有值,这时会调用equals()方法来 检查两个对象是否真的相同。如果两者相同,HashSet就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。这样就大大减少了equals的次数,相应就大大提高了执行速度。

如果两个对象相等,则hashcode一定也是相同的

两个对象相等,对两个对象分别调用equals方法都返回true

两个对象有相同的hashcode值,它们也不一定是相等的,因此,equals方法被覆盖过,则

hashCode方法也必须被覆盖

hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个 对象无论如何都不会相等(即使这两个对象指向相同的数据)

final、static关键字

\1. final关键字

\1. 修饰变量:这个变量必须在构造对象时就能够初始化,如果是基本数据类型的变量,那么这个 变量一旦初始化就不能更改,相当于一个常量,如果是引用数据类型的变量,则初始化后就不 能更改它的引用,不过引用对象变量的值可以更改

\2. 修饰方法:表明这个方法不能被子类重写,防止类修改它的含义

\3. 修饰类:表明这个类不能被继承,final类中的所有成员方法都会被隐式的指定为final方法, 但成员变量可是final也可以是非final。

\2. static关键字

\1. 修饰变量:static关键字修饰的变量也叫类变量,整个类共享这一个静态变量,使用类名.变量访问,与非静态变量区别,静态变量在内存中只有一个,随类加载而加载,程序结束时销毁, 非静态每个对象都有单独的一个,随着对象一起创建销毁

\2. 修饰方法:静态方法是不在对象上执行的方法,不需要类的实例化,可以直接通过类调用。在 类加载时分配内存空间(类中只有一个),在调用时执行,静态方法只能调用静态变量和静态 方法,且不能被重写。

\3. 修饰代码块:静态代码块只在类加载时执行一次,静态代码块及非静态代码块的执行顺序(1. 首先加载父类的静态字段或者静态语句块 2.子类的静态字段或静态语句块 3.父类普通变量以及语句块 4.父类构造方法被加载 5.子类变量或者语句块被加载 6.子类构造方法被加载)

\3. static和final关键字联用

\1. static final用在变量上相当于一个”全局变量“,一旦初始化后就不能被修改,并且可以通过类名调用,整个类共享一个

\2. static final用在方法上表明方法不能被子类修改(可以被继承被调用),并且可以通过类名调用

static关键字及其用法

\1. static修饰方法

static方法一般称作静态方法,由于静态方法不依赖于任何对象就可以进行访问,因此对于静态方法来说,是没有this的,因为它不依附于任何对象,既然都没有对象,就谈不上this了。

并且由于这个特性,在静态方法中不能访问类的非静态成员变量和非静态成员方法,因为非静态成 员方法/变量都是必须依赖具体的对象才能够被调用。

但是要注意的是,虽然在静态方法中不能访问非静态成员方法和非静态成员变量,但是在非静态成 员方法中是可以访问静态成员方法/变量的。

\2. static修饰变量

static变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。

而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本 互不影响。static成员变量的初始化顺序按照定义的顺序进行初始化。

\3. static修饰代码块

static块可以置于类中的任何地方,类中可以有多个static块。

在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。为什么说

static块可以用来优化程序性能,是因为它的特性:只会在类加载的时候执行一次。

String、StringBuffer、StringBuilder

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-juckQgzY-1645150357119)(file:///C:/Users/Ourway/AppData/Local/Packages/oice_16_974fa576_32c1d314_8c0/AC/Temp/msohtmlclip1/01/clip_image010.gif)]String是final修饰的,不可变的,每次操作都会产生新的String对象

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nlyoPjfX-1645150357120)(file:///C:/Users/Ourway/AppData/Local/Packages/oice_16_974fa576_32c1d314_8c0/AC/Temp/msohtmlclip1/01/clip_image010.gif)]StringBuffer和StringBuilder都是在原对象上操作的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HDjxvqDO-1645150357120)(file:///C:/Users/Ourway/AppData/Local/Packages/oice_16_974fa576_32c1d314_8c0/AC/Temp/msohtmlclip1/01/clip_image010.gif)]StringBuffer是线程安全的,StringBuilder线程不安全的,因为StringBuffer方法都是

synchronized修饰的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qs0AM1rG-1645150357121)(file:///C:/Users/Ourway/AppData/Local/Packages/oice_16_974fa576_32c1d314_8c0/AC/Temp/msohtmlclip1/01/clip_image010.gif)]性能:StringBuilder > StringBuffer > String

场景:经常需要改变字符串内容时使用后面两个

优先使用StringBuilder,多线程使用共享变量时使用StringBuffer

重载和重写的区别

重载: 发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。

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

接口和抽象类的区别

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ChQP5H2Z-1645150357121)(file:///C:/Users/Ourway/AppData/Local/Packages/oice_16_974fa576_32c1d314_8c0/AC/Temp/msohtmlclip1/01/clip_image011.gif)]抽象类中可以存在普通成员函数,而接口中只能存在public abstract方法

抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型抽象类只能继承一个,接口可以实现多个

List和Set的区别

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bXCx3uHM-1645150357122)(file:///C:/Users/Ourway/AppData/Local/Packages/oice_16_974fa576_32c1d314_8c0/AC/Temp/msohtmlclip1/01/clip_image007.gif)]List:有序,按对象进入的顺序保存对象,可重复,允许多个Null元素对象,可以使用Iterator取出 所有元素,在逐一遍历,还可以使用get(int index)获取指定下标的元素

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JXLZnchj-1645150357122)(file:///C:/Users/Ourway/AppData/Local/Packages/oice_16_974fa576_32c1d314_8c0/AC/Temp/msohtmlclip1/01/clip_image012.gif)]Set:无序,不可重复,最多允许一个Null元素对象,取元素时只能用Iterator接口取得所有元素, 再逐一遍历各个元素

ArrayList和LinkedList区别

ArrayList:基于动态数组,连续内存存储,适合下标访问(随机访问),存在扩容机制:因为数组长度固定,超出长度存数据时需要新建数组,然后将老数组的数据拷贝到新数组,如果不是尾部插入数据还 会涉及到元素的移动(往后复制一份,插入新元素),使用尾插法并指定初始容量可以极大提升性能、 甚至超过linkedList(需要创建大量的node对象)

LinkedList:基于链表,可以存储在分散的内存中,适合做数据插入及删除操作,不适合查询:需要逐一遍历

遍历LinkedList必须使用iterator不能使用for循环,因为每次for循环体内通过get(i)取得某一元素时都需 要对list重新进行遍历,性能消耗极大。

另外不要试图使用indexOf等返回元素索引,并利用其进行遍历,使用indexlOf对list进行了遍历,当结 果为空时会遍历整个列表。

HahsMap和HashTable有什么区别?其底层实现是什么

区别:

HashMap方法没有synchronized修饰,线程非安全,HashTable线程安全;

HashMap允许key和value为null,而HashTable不允许;

HashMap JDK1.8和JDK1.7的区别

\1. 底层数据结构不一样,1.7是数组+链表,1.8则是数组+链表+红黑树结构(当链表长度大于8,转为红黑树)。

\2. JDK1.8中resize()方法在表为空时,创建表;在表不为空时,扩容;而JDK1.7中resize()方法负责扩容,inflateTable()负责创建表。

\3. 1.8中没有区分键为null的情况,而1.7版本中对于键为null的情况调用putForNullKey()方法。但是 两个版本中如果键为null,那么调用hash()方法得到的都将是0,所以键为null的元素都始终位于哈希表table【0】中。

\4. 当1.8中的桶中元素处于链表的情况,遍历的同时最后如果没有匹配的,直接将节点添加到链表尾 部;而1.7在遍历的同时没有添加数据,而是另外调用了addEntry()方法,将节点添加到链表头部。

\5. 1.7中新增节点采用头插法,1.8中新增节点采用尾插法。这也是为什么1.8不容易出现环型链表的原因。

\6. 1.7中是通过更改hashSeed值修改节点的hash值从而达到rehash时的链表分散,而1.8中键的hash

值不会改变,rehash时根据(hash&oldCap)==0将链表分散。

\7. 1.8rehash时保证原链表的顺序,而1.7中rehash时有可能改变链表的顺序(头插法导致)。

\8. 在扩容的时候:1.7在插入数据之前扩容,而1.8插入数据成功之后扩容。

(当链表的长度特别长的时候,查询效率将直线下降,查询的时间复杂度为 O(n)。因此,JDK1.8 把它设计为达到一个特定的阈值之后,就将链表转化为红黑树,红黑树的定义:

\1. 每个节点只有两种颜色:红色或者黑色

\2. 根节点必须是黑色

\3. 每个叶子节点(NIL)都是黑色的空节点

\4. 从根节点到叶子节点,不能出现两个连续的红色节点

\5. 从任一节点出发,到它下边的子节点的路径包含的黑色节点数目都相同

由于红黑树,是一个自平衡的二叉搜索树(何节点的左右子树高度相差不超过1,是一种高度平衡的二叉 查找树。),因此可以使查询的时间复杂度降为O(logn)。

ConcurrentHashMap原理,JDK7和JDK8版本的区别

数据结构:ReentrantLock+Segment+HashEntry,一个Segment中包含一个HashEntry数组,每个HashEntry又是一个链表结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dtPgpbDy-1645150357123)(file:///C:/Users/Ourway/AppData/Local/Packages/oice_16_974fa576_32c1d314_8c0/AC/Temp/msohtmlclip1/01/clip_image014.gif)]元素查询:二次hash,第一次Hash定位到Segment,第二次Hash定位到元素所在的链表的 头部

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wNKmsVdA-1645150357123)(file:///C:/Users/Ourway/AppData/Local/Packages/oice_16_974fa576_32c1d314_8c0/AC/Temp/msohtmlclip1/01/clip_image015.gif)]锁:Segment分段锁 Segment继承了ReentrantLock,锁定操作的Segment,其他的

Segment不受影响,并发度为segment个数,可以通过构造函数指定,数组扩容不会影响其 他的segment

jdk8:

数据结构:synchronized+CAS+Node+红黑树,Node的val和next都用volatile修饰,保证可 见性查找,替换,赋值操作都使用CAS

锁:锁链表的head节点,不影响其他元素的读写,锁粒度更细,效率更高,扩容时,阻塞所有的读写操作、并发扩容

读操作无锁:

Node的val和next使用volatile修饰,读写线程对该变量互相可见数组用volatile修饰,保证扩容时被读线程感知

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

java中的编译器和解释器:

Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟的机器。这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口。

编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系 统的机器码执行。在Java中,这种供虚拟机理解的代码叫做 字节码(即扩展名为 .class的文件),它不面向任何特定的处理器,只面向虚拟机。

每一种平台的解释器是不同的,但是实现的虚拟机是相同的。Java源程序经过编译器编译后变成字节 码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机 器上的机器码,然后在特定的机器上运行。这也就是解释了Java的编译与解释并存的特点。

Java源代码---->编译器---->jvm可执行的Java字节码(即虚拟指令)---->jvm---->jvm中解释器----- >机器可执

行的二进制机器码— >程序运行。

采用字节码的好处:

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

JAVA中的异常体系

Java中的所有异常都来自顶级父类Throwable。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-naiZo40N-1645150357125)Throwable下有两个子类Exception和Error。

Error是程序无法处理的错误,一旦出现这个错误,则程序将被迫停止运行。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B0u8x4Wa-1645150357125)Exception不会导致程序停止,又分为两个部分RunTimeException运行时异常。

CheckedException检查异常。

RunTimeException常常发生在程序运行过程中,会导致程序当前线程执行失败。

CheckedException常常发生在程序编译过程中,会导致程序编译不通过。

JAVA类加载器

JDK自带有三个类加载器:bootstrap ClassLoader、ExtClassLoader、AppClassLoader。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-coX9EWCZ-1645150357126)BootStrapClassLoader是ExtClassLoader的父类加载器,默认负责加载lib目录下的jar包和class文 件。

ExtClassLoader是AppClassLoader的父类加载器,负责加载lib/ext文件夹下的jar包和class类。

AppClassLoader是自定义类加载器的父类,负责加载classpath下的类文件。系统类加载器,线程 上下文加载器

[若要自定义类加载器,则继承ClassLoader实现自定义类加载器

双亲委派模型

双亲委派的好处

主要是为了安全性,避免用户自己编写的类动态替换JAVA的一些核心类,比如****String**

同时也避免了类的重复加载,因为JVM中区分不同类,不仅仅是根据类名,相同的class文件被不 同的ClassLorader加载就是不同的两个类**

JAVA****中的异常体系

Java中所有异常都来自顶级父类Throwable

Throwable下有两个子类,Exception和Error

Error是程序无法处理的错误,一旦出现这个错误,则程序将被迫停止运行

Exception不会导致程序停止,又分为两个部分RunnTimeException运行时异常和

CheckedException检查异常

RunnTimeException常常发生在程序运行过程中,会导致程序当前线程执行失败,

CheckedException常常发生在程序编译的过程中,会导致程序编译不通过

GC是什么?为什么要有GC

GC是垃圾收集的意思,内存处理是开发人员容易出现问题的地方,忘记或者错误地内存回收会导致程序 或者系统的不稳定甚至崩溃,Java提供的垃圾回收机制可以自动检测对象是否超过作用域从而达到自动回收的目的。

JAVA垃圾回收机制

在Java开发中,程序员并不需要显式去释放一个对象的内存的,而是由虚拟机自动进行管理。在JVM

中,有一个低优先级的垃圾回收线程,在正常情况下这个线程是不会执行的,只有在虚拟机空闲或者当 前堆内存不足时,才会触发执行,扫描那些没有被任何引用的对象,并将它们添加到要回收的对象集合 中,然后进行回收操作。

GC如何判断对象可以被回收

两种方法:

引用计数法:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减去1,计 数为0时可以被回收

可达性分析法:从GC Roots开发向下搜索,搜索所走过的路径被称为引用链,当一个对象到GC

Roots没有任何引用链相连时,则证明对象是不可用的,那么虚拟机就判断该对象可进行回收

GC Roots的对象有:

虚拟机栈中引用的对象

方法区中类静态属性引用的对象方法区中常量引用的对象

本地方法栈中JNI引用的对象

可达性算法中的不可达对象并不是立即死亡的,对象拥有一次自我拯救的机会,对象被系统宣告死亡至 少还要经历两次标记过程:第一次是经过可达性分析发现没有与GC Roots相连接的引用链,第二次是在由虚拟机自动建立的Finalizer队列中判断是否需要执行finalize()方法。

当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收,否则,若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法,执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收, 否则,对象“复活”

JAVA创建线程的方式

第一种,通过继承Thread类创建线程类

1、定义一个类继承Thread类,并重写Thread类的run()方法,run()方法的方法体就是线程要完成的 任务,因此把run()称为线程的执行体;

2、创建该类的实例对象,即创建了线程对象;

3、调用线程对象的start()方法来启动线程;

第二种,通过实现Runnable接口创建线程类

1、定义一个类实现Runnable接口;

2、创建该类的实例对象obj;

3、将obj作为构造器参数传入Thread类实例对象,这个对象才是真正的线程对象;

4、调用线程对象的start()方法启动该线程;

第三种,通过Callable和Future接口创建线程

Callable接口提供了一个call()方法可以作为线程执行体

线程的生命周期?线程有哪些状态

线程通常有5种状态:创建、就绪、运行、阻塞、死亡

创建(New):新创建了一个线程对象

就绪(Runnable):线程对象创建后,其他线程调用了该对象的start方法,该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权

运行(Running):就绪状态的线程获取了CPU,执行了程序代码

阻塞:阻塞状态时线程因为某种原因放弃了CPU使用权。暂时停止运行,直到线程进入就绪状态, 才有机会转到运行状态
死亡:线程执行完了或者因异常退出了run方法,该线程结束生命周期

阻塞的情况分为三种:

等待阻塞:运行的线程执行wait方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中,进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify或notifyAll方法才能被 唤醒(wait是object类的方法)

同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中

其他阻塞:运行的线程执行sleep或join方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状 态,当sleep状态超时,join等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态

(sleep时Thread类的方法)

Lock和synchronized的区别

\1. 首先synchronized是java内置关键字,在jvm层面,Lock是个java类;

\2. synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;

\3. synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),

Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;

\4. 用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻 塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;

\5. synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)

\6. Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。 小例子:

sleep、wait、join、yield

sleep与wait的区别

\1. sleep时Thread类的静态本地方法,wait则是Object类的本地方法

\2. sleep方法不会释放lock,但是wait会释放,而且会加入到等待队列中

\3. sleep方法不依赖于同步器synchronized,但是wait需要依赖synchronized关键字

\4. sleep不需要被唤醒(休眠之后退出阻塞),但是wait需要

\5. sleep一般用于当前线程休眠,或者轮询暂停操作,wait则多用与线程之间的通信

\6. sleep会让出cpu执行时间且强制上下文切换,而wait则不一定,wait后可能还是有机会重新竞争到 锁继续执行的

yield():执行后线程直接进行就绪状态,马上释放了CPU的执行权,但是依旧保留了CPU的执行资格, 所以有可能CPU下次进行线程调度还会让这个线程获取到执行权继续执行

join():执行后线程进入阻塞状态,例如在线程B中调用线程A的join(),那线程B会进入阻塞队列,直到线 程A结束或者中断线程

对线程安全的理解

当多个线程访问一个对象时,如果不用进行额外的同步控制或其他的协同操作,调用这个对象的行为都 可以获得正确的结果,我们就说这个对象是线程安全的。

堆:是进程和线程共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户 分配的空间。堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是用完 了要还给操作系统,要不然就是内存泄漏。

堆:是Java虚拟机所管理的内存中最大的一块,是所有线程共享的一块内存区域,在虚拟机启动时创建。堆所存在唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。

栈:是每个线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的 栈互相独立,因此,栈是线程安全的。操作系统在切换线程的时候会自动切换栈。

在每个进程的内存空间中都会有一块特殊的公共区域,通常称为堆(内存)。进程内的所有线程都可以 访问到该区域,这就是造成问题的潜在原因。

对守护线程的理解

守护线程:为所有非守护线程提供服务的线程;任何一个守护线程都是整个JVM中所有非守护线程的保姆;

守护线程的作用是什么?

举例, GC垃圾回收线程:就是一个经典的守护线程,当我们的程序中不再有任何运行的Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是JVM上仅剩的线程时,垃圾回收线 程会自动离开。它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。

应用场景:(1)来为其它线程提供服务支持的情况;(2) 或者在任何情况下,程序结束时,这个线程必须正常且立刻关闭,就可以作为守护线程来使用;反之,如果一个正在执行某个操作的线程必须要 正确地关闭掉否则就会出现不好的后果的话,那么这个线程就不能是守护线程,而是用户线程。通常都 是些关键的事务,比方说,数据库录入或者更新,这些操作都是不能中断的。

thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个 IllegalThreadStateException

异常。你不能把正在运行的常规线程设置为守护线程。

在Daemon线程中产生的新线程也是Daemon的。

守护线程不能用于去访问固有资源,比如读写操作或者计算逻辑。因为它会在任何时候甚至在一个操作 的中间发生中断。

Java自带的多线程框架,比如ExecutorService,会将守护线程转换为用户线程,所以如果要使用后台线 程就不能用Java的线程池。

ThreadLocal的原理和使用场景

每一个Thread对象均含有一个ThreadLocalMap类型的成员变量threadLocals,它存储本线程中所有

ThreadLocal对象及其对应的值

当执行set方法时,ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key,将值存储进ThreadLocalMap对象中。

get方法执行过程类似。ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap

对象。再以当前ThreadLocal对象为key,获取对应的value。

由于每一条线程均含有各自****私有的********ThreadLocalMap容器,这些容器相互独立互不影响,因此不会存 在 线程安全性问题,从而也无需使用同步机制来保证多条线程访问容器的互斥性。

使用场景:

1、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。

2、线程间数据隔离

3、进行事务操作,用于存储线程事务信息。

4、数据库连接,Session会话管理。

(Spring框架在事务开始时会给当前线程绑定一个JDBC Connection,在整个事务过程都是使用该线程绑定的connection来执行数据库操作,实现了事务的隔离性,Spring框架里面就是用的

ThreadLocal来实现这种隔离)

ThreadLocal内存泄露原因,如何避免

不再会被使用的对象或者变量,占用的内存不能被回收,就是内存泄露。

强引用:使用最普遍的引用(new),一个对象具有强引用,不会被垃圾回收器回收。当内存空间不足,

Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不回收这种对象。

(如果想取消强引用和某个对象之间的关联,可以显式地将引用赋值为null,这样可以使JVM在合适的时间就会回收该对象。)

弱引用:JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用

java.lang.ref.WeakReference类来表示。可以在缓存中使用弱引用。

因此,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。

ThreadLocal正确的使用方法:每次使用完ThreadLocal都调用它的remove()方法清除数据将

ThreadLocal变量定义成private static,这样就一直存在ThreadLocal的强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉 。

并发、并行、串行的区别

串行在时间上不可能发生重叠,前一个任务没搞定,下一个任务就只能等着并行在时间上是重叠的,两个任务在同一时刻互不干扰的同时执行。

并发允许两个任务彼此干扰。统一时间点、只有一个任务运行,交替执行

并发的三大特性

原子性

原子性是指在一个操作中cpu不可以在中途暂停然后再调度,即不被中断操作,要不全部执行完成,要不都不执行。就好比转账,从账户A向账户B转1000元,那么必然包括2个操作:从账户A减去1000元, 往账户B加上1000元。2个操作必须全部完成。

有序性

保证程序最终执行结果和代码顺序执行的结果是一致的可见性

一个线程对共享变量的写入时,能对另一个线程可见

synchronized关键字

synchronized关键字可以繁殖线程干扰和内存一致性错误,如果一个对象时多个线程可见的,那么对该对象读写操作都将通过同步的方式来进行。

volatile、synchronized区别

\1. Java语言为了解决并发编程中存在的原子性、可见性和有序性问题,提供了一系列和并发处理相关的关键字,比如synchronized、volatile、final、concurren包等。

\2. synchronized通过加锁的方式,使得其在需要原子性、可见性和有序性这三种特性的时候都可以作为其中一种解决方案。

\3. volatile通过在volatile变量的操作前后插入内存屏障的方式,保证了变量在并发场景下的可见性和 有序性。

\4. volatile关键字是无法保证原子性的,而synchronized通过monitorenter和monitorexit两个指令, 可以保证被synchronized修饰的代码在同一时间只能被一个线程访问,即可保证不会出现CPU时间 片在多个线程间切换,即可保证原子性。

为什么使用线程池?解释下线程池参数

\1. 降低资源消耗;提高线程利用率,降低创建和销毁线程的消耗。

\2. 提高响应速度;任务来了,直接有线程可用可执行,而不是先创建线程,再执行。

\3. 提高线程的可管理性;线程是稀缺资源,使用线程池可以统一分配调优监控。

corePoolSize:代表核心线程数,也就是正常情况下创建工作的线程数,这些线程创建后并不会 消除 , 而 是 一 种 常 驻 线 程 maxinumPoolSize:代表的是最大线程数量,它与核心线程数相对应,表示最大允许被创建的线程数,比如当前任务较多,将核心线程数都用完了,还无法满足需求时,此时就会创建新的线程, 但 是线程池内线程总数不会超过最大线程数

keepAliveTime、unit:表示超出核心线程数之外的线程的空闲存活时间,也就是核心线程不会 消除,但是超出核心线程数的部分线程如果空闲一定的时间则会被消除,我们可以通过

setKeepAliveTime 来 设 置 空 闲 时 间
workQueue:用来存放待执行的任务,假设我们现在核心线程都已被使用,还有任务进来则全部 放 入队列,直到整个队列被放满但任务还再持续进入则会开始创建新的线程ThreadFactory:实际上是一个线程工厂,用来生产线程执行任务。我们可以选择使用默认的创建工厂,产生的线程都在同一个组内,拥有相同的优先级,且都不是守护线程。当然我们也可以选择 自定义线程工厂,一般我们会根据业务来制定不同的线程工厂 Handler:任务拒绝策略,有两种情况,第一种是当我们调用 shutdown 等方法关闭线程池后,这时候即使线程池内部还有没执行完的任务正在执行,但是由于线程池已经关闭,我们再继续想线程 池提交任务就会遭到拒绝。另一种情况就是当达到最大线程数,线程池已经没有能力继续处理新提 交的任务时,这是也就拒绝

线程池的处理流程

线程池中阻塞队列的作用?为什么是先添加队列而不是先创建 最大线程?

\1. 一般的队列只能保证作为一个有限长度的缓冲区,如果超出了缓冲长度,就无法保留当前的任务 了,阻塞队列通过阻塞可以保留住当前想要继续入队的任务。

\2. 阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程进入wait状态,释放cpu 资源。

\3. 阻塞队列自带阻塞和唤醒的功能,不需要额外处理,无任务执行时,线程池利用阻塞队列的take方法挂 起,从而维持核心线程的存活、不至于一直占用cpu资源

\4. 在创建新线程的时候,是要获取全局锁的,这个时候其它的就得阻塞,影响了整体效率

线程池中线程复用原理

在线程池中,同一个线程可以从阻塞队列中不断获取新任务来执行,其核心原理在于线程池对 Thread

进行了封装,并不是每次执行任务都会调用 Thread.start() 来创建新线程,而是让每个线程去 执行一个

“循环任务”,在这个“循环任务”中不停检查是否有任务需要被执行,如果有则直接执行,也就 是调用任务中的 run 方法,将 run 方法当成一个普通的方法执行,通过这种方式只使用固定的线程就 将所有任务的 run 方法串联起来。

Spring AOP 的理解

系统是由许多不同的组件所组成的,每一个组件各负责一块特定功能。除了实现自身核心功能之外,这 些组件还经常承担着额外的职责。例如日志、事务管理和安全这样的核心服务经常融入到自身具有核心 业务逻辑的组件中去。这些系统服务经常被称为横切关注点,因为它们会跨越系统的多个组件。

当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从 上到下的关系,但并不适合定义从左到右的关系。例如日志功能。

AOP:将程序中的交叉业务逻辑(比如安全,日志,事务等),封装成一个切面,然后注入到目标对象

(具体业务逻辑)中去。AOP可以对某个对象或某些对象的功能进行增强,比如对象中的方法进行增强,可以在执行某个方法之前额外的做一些事情,在某个方法执行之后额外的做一些事情。

AOP动态代理的原理:

\1. JDK动态代理:JDK的动态代理是基于反射实现。JDK通过反射,生成一个代理类,这个代理类实现 了原来那个类的全部接口,并对接口中定义的所有方法进行了代理。当我们通过代理对象执行原来 那个类的方法时,代理类底层会通过反射机制,回调我们实现的InvocationHandler接口的invoke 方法。并且这个代理类是Proxy类的子类。

\2. CGLib代理:CGLib实现动态代理的原理是,底层采用了ASM字节码生成框架,直接对需要代理的类的字节码进行操作,生成这个类的一个子类,并重写了类的所有可以重写的方法,在重写的过程 中,将我们定义的额外的逻辑(简单理解为Spring中的切面)织入到方法中,对方法进行了增强。而通过字节码操作生成的代理类,和我们自己编写并编译后的类没有太大区别。

Spring IOC的理解

从三个维度:容器、控制反转、依赖注入

IOC容器:

实际上就是个map(key,value),里面存的是各种对象(在xml里配置的bean节点、

@repository、@service、@controller、@component),在项目启动的时候会读取配置文件里面的

bean节点,根据全限定类名使用反射创建对象放到map里、扫描到打上上述注解的类还是通过反射创 建对象放到map里。这个时候map里就有各种对象了,接下来我们在代码里需要用到里面的对象时,再通过DI注入(autowired、resource等注解,xml里bean节点内的ref属性,项目启动的时候会读取xml 节点ref属性 根据id注入,也会扫描这些注解,根据类型或id注入;id就是对象名)。

控制反转:

没有引入IOC容器之前,对象A依赖于对象B,那么对象A在初始化或者运行到某一点的时候,自己必 须 主动去创建对象B或者使用已经创建的对象B。无论是创建还是使用对象B,控制权都在自己手上。引入IOC容器之后,对象A与对象B之间失去了直接联系,当对象A运行到需要对象B的时候,IOC容器会 主动创建一个对象B注入到对象A需要的地方。

全部对象的控制权全部上缴给“第三方”IOC容器,所以,IOC容器成了整个系统的关键核心,它起到了一 种类似“粘合剂”的作用,把系统中的所有对象粘合在一起发挥作用,如果没有这个“粘合剂”,对象与对 象之间会彼此失去联系,这就是有人把IOC容器比喻成“粘合剂”的由来。

依赖注入:

“获得依赖对象的过程被反转了”。控制被反转之后,获得依赖对象的过程由自身管理变为了由IOC容器主动注入。依赖注入是实现IOC的方法,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对 象之中。

BeanFactory和ApplicationContext有什么区别

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BE4LhzHQ-1645150357138)(file:///C:/Users/Ourway/AppData/Local/Packages/oice_16_974fa576_32c1d314_8c0/AC/Temp/msohtmlclip1/01/clip_image034.gif)]BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用

getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问

题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法 才会 抛 出 异 常 。 ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我 们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入ApplicationContext 启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需要的 时候,你就不用等

待,因为它们已经创建好了。

]相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean 较 多 时 , 程 序 启 动 较 慢 。 BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用

ContextLoader。

BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使 用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。

Spring Bean 的生命周期

\1. 解析类得到BeanDefinition

\2. 如果有多个构造方法,则要推断构造方法

\3. 确定好构造方法后,进行实例化得到一个对象

\4. 对对象中的加了@Autowired注解的属性进行属性填充

\5. 回调Aware方法,比如BeanNameAware,BeanFactoryAware

\6. 调用BeanPostProcessor的初始化前的方法

\7. 调用初始化方法

\8. 调用BeanPostProcessor的初始化后的方法,在这里会进行AOP

\9. 如果当前创建的bean是单例的则会把bean放入单例池

\10. 使用bean

\11. Spring容器关闭时调用DisposableBean中destory()方法

解释下Spring支持的几种bean的作用域

singleton:默认,每个容器中只有一个bean的实例,单例的模式由BeanFactory自身来维护。该 对象的生命周期是与Spring IOC容器一致的(但在第一次被注入时才会创建)。

prototype:为每一个bean请求提供一个实例。在每次注入时都会创建一个新的对象

request:bean被定义为在每个HTTP请求中创建一个单例对象,也就是说在单个请求中都会复用 这一个单例对象。

session:与request范围类似,确保每个session中有一个bean的实例,在session过期后,bean

会随之失效。

application:bean被定义为在ServletContext的生命周期中复用一个单例对象。 websocket:

bean被定义为在websocket的生命周期中复用一个单例对象。

Spring框架中的单例Bean是线程安全的么

Spring中的Bean默认是单例模式的,框架并没有对bean进行多线程的封装处理。

如果Bean是有状态的,那就需要开发人员自己来进行线程安全的保证,最简单的办法就是改变bean的 作 用域 把 "singleton"改为’‘protopyte’ 这样每次请求Bean就相当于是 new Bean() 这样就可以保证线程的 安全了。

有状态就是有数据存储功能,无状态就是不会保存数据:controller、service和dao层本身并不是线程安 全的,只是如果只 是调用里面的方法,而且多线程调用一个实例的方法,会在内存中复制变量,这是自己的线程的工 作内存,是安全的。

Dao会操作数据库Connection,Connection是带有状态的,比如说数据库事务,Spring的事务管理器 使用Threadlocal为不同线程维护了一套独立的connection副本,保证线程之间不会互相影响(Spring 是如何保证事务获取同一个Connection的)

不要在bean中声明任何有状态的实例变量或类变量,如果必须如此,那么就使用ThreadLocal把变量变 为线程私有的,如果bean的实例变量或类变量需要在多个线程之间共享,那么就只能使用

synchronized、lock、CAS等这些实现线程同步的方法了。

Spring框架中都用到了哪些设计模式

\1. 工厂设计模式** : Spring使用工厂模式通过 BeanFactory 、 ApplicationContext 创建 bean 对象。

\2. 代理设计模式 : Spring AOP 功能的实现

\3. 单例设计模式 : Spring 中的 Bean 默认都是单例的

\4. 模板方法模式 : Spring 中 jdbcTemplate 、 hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。

\5. 包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。

\6. 观察者模式**😗* Spring 事件驱动模型就是观察者模式很经典的一个应用。

\7. 适配器模式 :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller

Spring事务的实现方式和原理以及隔离级别

在使用Spring框架时,可以有两种使用事务的方式,一种是编程式的,一种是申明式的,

@Transactional注解就是申明式的。

首先,事务这个概念是数据库层面的,Spring只是基于数据库中的事务进行了扩展,以及提供了一些能让程序员更加方便操作事务的方式。

比如我们可以通过在某个方法上增加@Transactional注解,就可以开启事务,这个方法中所有的sql都 会在一个事务中执行,统一成功或失败。

在一个方法上加了@Transactional注解后,Spring会基于这个类生成一个代理对象,会将这个代理对象 作为bean,当在使用这个代理对象的方法时,如果这个方法上存在@Transactional注解,那么代理逻辑会先把事务的自动提交设置为false,然后再去执行原本的业务逻辑方法,如果执行业务逻辑方法没有出现异常,那么代理逻辑中就会将事务进行提交,如果执行业务逻辑方法出现了异常,那么则会将事务 进行回滚。

当然,针对哪些异常回滚事务是可以配置的,可以利用@Transactional注解中的rollbackFor属性进行 配置,默认情况下会对RuntimeException和Error进行回滚。

spring事务隔离级别就是数据库的隔离级别: read uncommitted(未提交读)

read committed(提交读、不可重复读)

repeatable read(可重复读) serializable(可串行化)

Spring 事务传播机制

REQUIRED(Spring默认的事务传播类型):如果当前没有事务,则自己新建一个事务,如果当前存 在 事 务 , 则 加 入 这 个 事 务SUPPORTS:当前存在事务,则加入当前事务,如果当前没有事务,就以非事务方法执行MANDATORY:当前存在事务,则加入当前事务,如果当前事务不存在,则抛出异常。REQUIRES_NEW:创建一个新事务,如果存在当前事务,则挂起该事务。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1kbNSIPy-1645150357143)(file:///C:/Users/Ourway/AppData/Local/Packages/oice_16_974fa576_32c1d314_8c0/AC/Temp/msohtmlclip1/01/clip_image045.gif)]NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,则挂起当前事务 NEVER:不使用事务,如果当前事务存在,则抛出异常

NESTED:如果当前事务存在,则在嵌套事务中执行,否则REQUIRED的操作一样(开启一个事 务)

Spring事务什么时候会失效

发生自调用,类里面使用this调用本类的方法(this通常省略),此时这个this对象不是代理类,而是UserService对象本身!(解决方法很简单,让那个this变成UserService的代理类即可!)

方法不是public的数据库不支持事务

没有被spring管理 5、异常被吃掉,事务不会回滚

什么是bean的自动装配,有哪些方式

开启自动装配,只需要在xml配置文件中定义“autowire”属性。

autowire属性有五种装配的方式:

no – 缺省情况下,自动配置是通过“ref”属性手动设定 。

byName-根据bean的属性名称进行自动装配。byType-根据bean的类型进行自动装配。

constructor-类似byType,不过是应用于构造器的参数。如果一个bean与构造器参数的类型形 同,则进行自动装配,否则导致异常。

autodetect-如果有默认的构造器,则通过constructor方式进行自动装配,否则使用byType方式进行自动装配。

springmvc、springBoot、spring有什么区别

spring是一个IOC容器,用来管理Bean,使用依赖注入实现控制反转,可以很方便的整合各种框架,提 供AOP机制弥补OOP的代码重复问题、更方便将不同类不同方法中的共同处理抽取成切面、自动注入给 方法执行,比如日志、异常等

springmvc是spring对web框架的一个解决方案,提供了一个总的前端控制器Servlet,用来接收请 求, 然后定义了一套路由策略(url到handle的映射)及适配执行handle,将handle结果使用视图解 析 技 术 生 成 视 图 展 现 给 前 端springboot是spring提供的一个快速开发工具包,让程序员能更方便、更快速的开发

spring+springmvc 应用,简化了配置(约定了默认配置),整合了一系列的解决方案(starter机制)、redis、 mongodb、es,可以开箱即用

SpringMVC工作流程

\1. 用户发送请求至前端控制器 DispatcherServlet

\2. DispatcherServlet 收到请求调用 HandlerMapping 处理器映射器。

\3. 处理器映射器找到具体的处理器,生成处理器及处理器拦截器,一并返回给 DispatcherServlet

\4. DispatcherServlet 调用 HandlerAdapter 处理器适配器

\5. HandlerAdapter 经过适配调用具体的处理器(Controller,也叫后端控制器)

\6. Controller 执行完成返回 ModelAndView

\7. HandlerAdapter 将 controller 执行结果 ModelAndView 返回给 DispatcherServlet

\8. DispatcherServlet 将 ModelAndView 传给 ViewReslover 视图解析器

\9. ViewReslover 解析后返回具体 View

\10. DispatcherServlet 根据 View 进行渲染视图(即将模型数据填充至视图中)

\11. DispatcherServlet 响应用户

SpringMVC的主要组件

前端控制器(DispatcherServlet):主要负责捕获来自客户端的请求和调度各个组件。

处理器映射器(HandlerMapping):根据url查找后端控制器Handler

处理器适配器(HandlerAdapter):执行后端控制器(Handler),拿到后端控制器返回的结果

ModelAndView后将结果返回给前端控制器DispatcherServlet。

视图解析器(ViewResolver):主要负责将从DispatcherServlet中拿到的ModelAndView对象进行解析,生成View对象返回给DispatcherServlet。

MySQL日志

\1. 错误日志――MySQL服务启动和关闭过程中的信息以及其它错误和警告信息。默认在数据目录下。

\2. 一般查询日志――用于记录select查询语句的日志。general_log、general_log_file 默认关闭,建议关闭。

\3. 慢查询日志――记录所有超过long_query_time时间的SQL语句,

\4. 二进制日志――记录任何引起数据变化的操作,用于备份和还原。默认存放在数据目录中,在刷新 和服务重启时会滚动二进制日志。

\5. 中继日志――从主服务器的二进制文件中复制的事件,并保存为二进制文件,格式和二进制日志一 样。

\6. 事务日志――保证事务的一致性。

MySQL三范式

1NF:数据库表中的任何属性都具有原子性,不可再分解

2NF:对记录的唯一性约束,要求记录由唯一标识

3NF:对字段冗余的约束,即任何字段不能由其他字段派生出来,它要求字段没有冗余

数据库左连接、右连接、内连接、全连接

left join (左连接):返回包括左表中的所有记录和右表中连接字段相等的记录。right join (右连接):返回包括右表中的所有记录和左表中连接字段相等的记录。inner join (等值连接或者叫内连接):只返回两个表中连接字段相等的行。

full join (全外连接):返回左右表中所有的记录和左右表中连接字段相等的记录。

索引的基本原理

索引用来快速地寻找哪些具有特定值的记录,如果没有索引,一般来说执行查询遍历整张表。索引的原 理:就是把无序的数据变成有序的数据

\1. 把创建了索引的列的内容进行排序

\2. 对排序的结果生产倒排表

\3. 在倒排内容上拼上数据地址链

\4. 在查询的时候,先拿到倒排表内容,再去除数据地址链,从而拿到具体数据

mysql聚族索引和非聚族索引的区别

都是B+数的数据结构

聚簇索引:将数据存储与索引放到了一块、并且是按照一定的顺序组织的,找到索引也就找到了数 据, 数据的物理存放顺序与索引顺序是一致的,即:只要索引是相邻的,那么对应的数据一定也是 相邻地存放在磁盘上的

非聚簇索引:叶子节点不存储数据、存储的是数据行地址,也就是说根据索引查找到数据行的位置 再取磁盘查找数据,这个就有点类似一本树的目录,比如我们要找第三章第一节,那我们先在这个 目录里面找,找到对应的页码后再去对应的页码看文章。

区别:

\1. 查询通过聚簇索引可以直接获取数据,相比非聚簇索引需要第二次查询(非覆盖索引的情况下)效 率要高

\2. 聚簇索引对于范围查询的效率很高,因为其数据是按照大小排列的

\3. 聚簇索引适合用在排序的场合,非聚簇索引不适合

\4. 维护索引很昂贵,特别是插入新行或者主键被更新导至要分页(page split)的时候。建议在大量插入新行后,选在负载较低的时间段,通过OPTIMIZE TABLE优化表,因为必须被移动的行数据可能造成 碎片。使用独享表空间可以弱化碎片

\5. 表因为使用UUId(随机ID)作为主键,使数据存储稀疏,这就会出现聚簇索引有可能有比全表扫面 更慢,所以建议使用int的auto_increment作为主键

\6. 如果主键比较大的话,那辅助索引将会变的更大,因为辅助索引的叶子存储的是主键值;过长的主 键 值,会导致非叶子节点占用占用更多的物理空间

mysql索引的数据结构,各自优点和缺点

索引的数据结构和具体存储引擎的实现有关,

在MySQL中使用较多的索引有Hash索引,B+树索引等,

InnoDB存储引擎的默认索引实现为:B+树索引。

对于哈希索引来说,底层的数据结构就是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选 择哈希索引,查询性能最快;其余大部分场景,建议 选择B+数索引。

B+树:

B+树是一个平衡的多叉树,从根节点到每个叶子节点的高度差值不超过1,而且同层级的节点间有指针 相互链接。在B+树上的常规检索,从根节点到叶子节点的搜索效率基本相当,不会出现大幅波动,而且 基于索引的顺序扫描时,也可以利用双向指针快速左右移动,效率非常高。因此,B+树索引被广泛应用 于数据库、文件系统等场景。

(B和B+的区别:

\1. 关键字的数量不同,b+树有m个关键字,也有m个叶子节点。关键字只是用来存储索引。B树虽然也有m个子结点,但是其只拥有m-1个关键字。

\2. 存储的位置不同;B+树中的数据都存储在叶子结点上,也就是其所有叶子结点的数据组合起来就是 完整的数据,但是B树的数据存储在每一个结点中,并不仅仅存储在叶子结点上。

\3. 分支结点的构造不同;B+树的分支结点仅仅存储着关键字信息和儿子的指针(这里的指针指的是磁 盘块的偏移量),也就是说内部结点仅仅包含着索引信息。

\4. 查询不同;B树在找到具体的数值以后,则结束,而B+树则需要通过索引找到叶子结点中的数据才结束,也就是说B+树的搜索过程中走了一条从根结点到叶子结点的路径。)

哈希索引:

哈希索引就是采用一定的哈希算法,把键值换算成新的哈希值,检索时不需要类似B+树那样从根节点到 叶子节点逐级查找,只需一次哈希算法即可立刻定位到相应的位置,速度非常快

如果是等值查询,那么哈希索引明显有绝对优势,因为只需要经过一次算法即可找到相应的键值;前提 是键值都是唯一的。如果键值不是唯一的,就需要先找到该键所在位置,然后再根据链表往后扫描,直 到找到相应的数据;

如果是范围查询检索,这时候哈希索引就毫无用武之地了,因为原先是有序的键值,经过哈希算法后, 有可能变成不连续的了,就没办法再利用索引完成范围查询检索;

哈希索引也没办法利用索引完成排序,以及like ‘xxx%’ 这样的部分模糊查询(这种部分模糊查询,其实本质上也是范围查询);

哈希索引也不支持多列联合索引的最左匹配规则;

B+树索引的关键字检索效率比较平均,不像B树那样波动幅度大,在有大量重复键值情况下,哈希索引的效率也是极低的,因为存在哈希碰撞问题。

mysql索引设计原则

查询更快、占用空间更小

\1. 适合索引的列是出现在where子句中的列,或者连接子句中指定的列

\2. 基数较小的表,索引效果较差,没有必要在此列建立索引

\3. 使用短索引,如果对长字符串列进行索引,应该指定一个前缀长度,这样能够节省大量索引空间, 如果搜索词超过索引前缀长度,则使用索引排除不匹配的行,然后检查其余行是否可能匹配

\4. 不要过度索引。索引需要额外的磁盘空间,并降低写操作的性能。在修改表内容的时候,索引会进 行更新甚至重构,索引列越多,这个时间就会越长。所以只保持需要的索引有利于查询即可。

\5. 定义有外键的数据列一定要建立索引。

\6. 更新频繁字段不适合创建索引

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

\8. 尽量的扩展索引,不要新建索引。比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。

\9. 对于那些查询中很少涉及的列,重复值比较多的列不要建立索引。

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

什么情况下索引会失效

\1. 如果条件中有or,即使其中有条件带索引也不会使用(这也是为什么尽量少用or的原因)

\2. 对于多列索引,不是使用的第一部分,则不会使用索引
3.like查询是以%开头
4.如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引

\5. 如果mysql估计使用全表扫描要比使用索引快,则不使用索引

mysql锁的类型有哪些

共享锁

共享锁又称读锁,简称S锁;当一个事务为数据加上读锁之后,其他事务只能对该数据加读

锁,而不能对数据加写锁,直到所有的读锁释放之后其他事务才能对其进行加持写锁。共享锁 的特性主要是为了支持 并发的读取数据,读取数据的时候不支持修改,避免出现重复读的问题。

排他锁

排他锁又称写锁,简称X锁;当一个事务为数据加上写锁时,其他请求将不能再为数据加任何 锁,直到该锁释放之后,其他事务才能对数据进行加锁。排他锁的目的是在数据修改时候,不 允许其他人同时修 改,也不允许其他人读取。避免了出现脏数据和脏读的问题。

表锁

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0iEk3IbT-1645150357151)(file:///C:/Users/Ourway/AppData/Local/Packages/oice_16_974fa576_32c1d314_8c0/AC/Temp/msohtmlclip1/01/clip_image050.gif)]表锁是指上锁的时候锁住的是整个表,当下一个事务访问该表的时候,必须等前一个事务释放 了锁才能进行对表进行访问;

特点: 粒度大,加锁简单,容易冲突;

行锁

行锁是指上锁的时候锁住的是表的某一行或多行记录,其他事务访问同一张表时,只有被锁住 的记录不能访问,其他的记录可正常访问;

特点:粒度小,加锁比表锁麻烦,不容易冲突,相比表锁支持的并发要高;

记录锁

记录锁也属于行锁中的一种,只不过记录锁的范围只是表中的某一条记录,记录锁是说事务在 加锁后锁住的只是表的某一条记录。 精准条件命中,并且命中的条件字段是唯一索引

加了记录锁之后数据可以避免数据在查询的时候被修改的重复读问题,也避免了在修改的事务 未提交前 被其他事务读取的脏读问题。

页锁

页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多, 行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。 特点:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般

间隙锁

属于行锁中的一种,间隙锁是在事务加锁后其锁住的是表记录的某一个区间,当表的相邻ID 之间出现空隙则会形成一个区间,遵循左开右闭原则。 范围查询并且查询未命中记录,查询条件必须命中索引、间隙锁只会出现在REPEATABLE_READ(重复 读)的事务级别中。 触发条件:防止幻读问题,事务并发的时候,如果没有间隙锁,就会发生如下图的问题,在同一个事 务 里,A事务的两次查询出的结果会不一样。

临建锁

也属于行锁的一种,并且它是INNODB的行锁默认算法,总结来说它就是记录锁和间隙锁的组合,临键锁会把查询出来的记录锁住,同时也会把该范围查询内的所有间隙空间也会锁住,再 之它会把相邻的下一 个区间也会锁住

触发条件:范围查询并命中,查询命中了索引。 结合记录锁和间隙锁的特性,临键锁避免了在范围查询时出现脏读、重复读、幻读问题。加了临键锁之 后,在范围区间内数据不允许被修改和插入。

意向锁

如果当事务A加锁成功之后就设置一个状态告诉后面的人,已经有人对表里的行加了一个排他 锁 了,你们不能对整个表加共享锁或排它锁了,那么后面需要对整个表加锁的人只需要获取这个状态 就知道自己是不是可以对表加锁,避免了对整个索引树的每个节点扫描是否加锁, 而这个状态就是 意向锁。

关心过业务系统里面的sql耗时吗?统计过慢查询吗?对慢查询都怎么优化?

在业务系统中,除了使用主键进行的查询,其他的都会在测试库上测试其耗时,慢查询的统计主要由运 维在做,会定期将业务中的慢查询反馈给我们。

优化:

首先分析语句,看看是否load了额外的数据,可能是查询了多余的行并且抛弃掉了,可能是加载 了许多结果中并不需要的列,对语句进行分析以及重写。 分析语句的执行计划,然后获得其使用索引的情况, 之后修改语句或者修改索引,使得语句可以尽 可能的命中索引。 如果对语句的优化已经无法进行,可以考虑表中的数据量是否太大,如果是的话可以进行横向或者 纵向的分表。

事务的基本特性和隔离级别?脏读、不可重复读、幻读?

事务基本特性ACID分别是:

原子性:指的是一个事务中的操作要么全部成功,要么全部失败

一致性:指的是数据库总是从一个一致性的状态转换到另外一个一致性的状态。比如A转账给B100 块钱, 假设A只有90块,支付之前我们数据库里的数据都是符合约束的,但是如果事务执行成功了, 我们的数据库 数据就破坏约束了,因此事务不能成功,这里我们说事务提供了一致性的保证

隔离性:指的是一个事务的修改在最终提交前,对其他事务是不可见的持久性:指的是一旦事务提交,所做的修改就会永久保存到数据库中。

隔离的4个级别:

read uncommit :读未提交,可能会读到其他事务未提交的数据,也叫做脏读

read commit:读已提交,两次读取结果不一致,叫做不可重复读。 不可重复读解决了脏读的问题,他只会读取已经提交的事务。

repeatable read:可重复复读,这是mysql的默认级别,就是每次读取结果都一样,但是有可能产生 幻 读 。 serializable:串行,一般是不会使用的,他会给每一行读取的数据加锁,会导致大量超时和锁竞争的问题。

脏读(Drity Read):某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因, 前一个RollBack了操作,则后一个事务所读取的数据就会是不正确的。

不可重复读(Non-repeatable read):在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新的原有的数据。

幻读(Phantom Read):在一个事务的两次查询中数据笔数不一致,例如有一个事务查询了几列(Row)数据,而另一个事务却在此时插入了新的几列数据,先前的事务在接下来的查询中,就会发现有几列数据 是它先前所没有的。

ACID靠什么保证的?

A原子性:由undo log日志保证,它记录了需要回滚的日志信息,事务回滚时撤销已经执行成功的sql C

一致性:由其他三大特性保证、程序代码要保证业务上的一致性

I隔离性:由MVCC来保证

D持久性:由内存+redo log来保证,mysql修改数据同时在内存和redo log记录这次操作,宕机的时候可 以从redo log恢复

什么是MVCC

多版本并发控制:读取数据时通过一种类似快照的方式将数据保存下来,这样读锁就和写锁不冲突了, 不同的事务session会看到自己特定版本的数据,版本链MVCC只在 READ COMMITTED 和 REPEATABLE

READ 两个隔离级别下工作。其他两个隔离级别够和 MVCC不兼容, 因为 READ UNCOMMITTED 总是读取最新的数据行, 而不是符合当前事务版本的数据 行。而 SERIALIZABLE 则会对所有读取的行都加锁。

mysql主从同步原理

Mysql的主从复制中主要有三个线程:master、slave,Master一条线程和Slave中的两条线程

主节点 binlog,主从复制的基础是主库记录数据库的所有变更记录到 binlog。binlog 是数据库服务器启动的那一刻起,保存所有修改数据库结构或内容的一个文件

主节点 log dump 线程,当 binlog 有变动时,log dump 线程读取其内容并发送给从节点。从节点 I/O线程接收 binlog 内容,并将其写入到 relay log 文件中。

从节点的SQL 线程读取 relay log 文件内容对数据更新进行重放,最终保证主从数据库的一致性。

由于mysql默认的复制方式是异步的,主库把日志发送给从库后不关心从库是否已经处理,这样会产生 一个问题就是假设主库挂了,从库处理失败了,这时候从库升为主库后,日志就丢失了。由此产生两个 概念。

全同步复制:

主库写入binlog后强制同步日志到从库,所有的从库都执行完成后才返回给客户端,但是很显然这个方式的话性能会受到严重影响。

半同步复制:

和全同步不同的是,半同步复制的逻辑是这样,从库写入日志成功后返回ACK确认给主库,主库收到至少一个从库的确认就认为写操作完成。

简述MyISAM和InnoDB的区别

MyISAM:

不支持事务,但是每次查询都是原子的; 支持表级锁,即每次操作是对整个表加锁; 存储表的总行数;

一个MYISAM表有三个文件:索引文件、表结构文件、数据文件;

采用非聚集索引,索引文件的数据域存储指向数据文件的指针。辅索引与主索引基本一致,但是辅索引 不用保证唯一性。

InnoDB:

支持ACID的事务,支持事务的四种隔离级别; 支持行级锁及外键约束:因此可以支持写并发;

不存储总行数;一个InnoDb引擎存储在一个文件空间(共享表空间,表大小不受操作系统控制,一个表可能分布在多个文件里),也有可能为多个(设置为独立表空,表大小受操作系统文件大小限制,一般 为2G),受操作系统文件大小的限制;

主键索引采用聚集索引(索引的数据域存储数据文件本身),辅索引的数据域存储主键的值;因此从辅 索引查找数据,需要先通过辅索引找到主键值,再访问辅索引;最好使用自增主键,防止插入数据时, 为维持B+树结构,文件的大调整。

简述mysql中索引类型及对数据库的性能的影响

mysql中索引类型

普通索引:允许被索引的数据列包含重复的值。唯一索引:可以保证数据记录的唯一性。

主键:是一种特殊的唯一索引,在一张表中只能定义一个主键索引,主键用于唯一标识一条记录, 使用关键字 PRIMARY KEY 来创建。

联合索引:索引可以覆盖多个数据列,如像INDEX(columnA, columnB)索引。

全文索引:通过建立 倒排索引 ,可以极大的提升检索效率,解决判断字段是否包含的问题,是目前搜索引擎使用的一种关键技术。可以通过ALTER TABLE table_name ADD FULLTEXT (column);创建全文索引

对数据库的性能的影响:

索引可以极大的提高数据的查询速度。

通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。

但是会降低插入、删除、更新表的速度,因为在执行这些写操作时,还要操作索引文件

索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建 立聚簇索引,那么需要的空间就会更大,如果非聚集索引很多,一旦聚集索引改变,那么所有非聚 集索引都会跟着变。

mysql执行计划怎么看

执行计划就是sql的执行查询的顺序,以及如何使用索引查询,返回的结果集的行数

EXPLAIN SELECT * from A where X=? and Y=?

1、id :是一个有顺序的编号,是查询的顺序号,有几个 select 就显示几行。id的顺序是按 select 出现的顺序增长的。id列的值越大执行优先级越高越先执行,id列的值相同则从上往下执行,id列的值为

NULL最后执行。

2、selectType 表示查询中每个select子句的类型

SIMPLE: 表示此查询不包含 UNION 查询或子查询

PRIMARY: 表示此查询是最外层的查询(包含子查询)

SUBQUERY: 子 查 询 中 的 第 一 个 SELECT UNION: 表示此查询是 UNION 的第二或随后的查询

DEPENDENT UNION: UNION 中的第二个或后面的查询语句, 取决于外面的查询

UNION RESULT, UNION 的结果

DEPENDENT SUBQUERY: 子查询中的第一个 SELECT, 取决于外面的查询. 即子查询依赖于外层查询的结果.

DERIVED:衍生,表示导出表的SELECT(FROM子句的子查询)

3、table:表示该语句查询的表

4、type:优化sql的重要字段,也是我们判断sql性能和优化程度重要指标。他的取值类型范围: const:通过索引一次命中,匹配一行数据

system: 表中只有一行记录,相当于系统表;

eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配

ref: 非唯一性索引扫描,返回匹配某个值的所有

range: 只检索给定范围的行,使用一个索引来选择行,一般用于between、<、>;

index: 只遍历索引树;

ALL: 表示全表扫描,这个类型的查询是性能最差的查询之一。 那么基本就是随着表的数量增多, 执行效率越慢。

执行效率:

ALL < index < range< ref < eq_ref < const < system*。最好避免ALL和index

5、possible_keys:它表示Mysql在执行该sql语句的时候,可能用到的索引信息,仅仅是可能,实际不 一

定会用到。

6、key:此字段是 mysql 在当前查询时所真正使用到的索引。 他是possible_keys的子集

7、key_len:表示查询优化器使用了索引的字节数,这个字段可以评估组合索引是否完全被使用,这也是我们优化sql时,评估索引的重要指标

9、rows:mysql 查询优化器根据统计信息,估算该sql返回结果集需要扫描读取的行数,这个值相关重要,索引优化之后,扫描读取的行数越多,说明索引设置不对,或者字段传入的类型之类的问题,说明 要优化空间越大

10、filtered:返回结果的行占需要读到的行(rows列的值)的百分比,就是百分比越高,说明需要查询到 数据越准确, 百分比越小,说明查询到的数据量大,而结果集很少

11、extra

using filesort :表示 mysql 对结果集进行外部排序,不能通过索引顺序达到排序效果。一般有

using filesort都建议优化去掉,因为这样的查询 cpu 资源消耗大,延时大。

using index:覆盖索引扫描,表示查询在索引树中就可查找所需数据,不用扫描表数据文件,往往说明性能不错。using temporary:查询有使用临时表, 一般出现于排序, 分组和多表 join 的情况, 查询效率不高,建议优化。

using where :sql使用了where过滤,效率较高

RDB和AOF机制

RDB:Redis DataBase

在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写 入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。

优点:

\1. 整个Redis数据库将只包含一个文件 dump.rdb,方便持久化。

\2. 容灾性好,方便备份。

\3. 性能最大化,fork 子进程来完成写操作,让主进程继续处理命令,所以是 IO 最大化。使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 redis 的高性能

\4. 相对于数据集大时,比 AOF 的启动效率更高。

缺点:

\1. 数据安全性低。RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候

\2. 由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟

AOF:Append Only File

以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以 打开文件看到详细的操作记录

优点:

\1. 数据安全,Redis中提供了3中同步策略,即每秒同步、每修改同步和不同步。事实上,每秒同步也是异步完成的,其效率也是非常高的,所差的是一旦系统出现宕机现象,那么这一秒钟之内修改的 数据将会丢失。而每修改同步,我们可以将其视为同步持久化,即每次发生的数据变化都会被立即 记录到磁盘中。

\2. 通过 append 模式写文件,即使中途服务器宕机也不会破坏已经存在的内容,可以通过 redis- check-aof 工具解决数据一致性问题。

\3. AOF 机制的 rewrite 模式。定期对AOF文件进行重写,以达到压缩的目的

缺点:

\1. AOF 文件比 RDB 文件大,且恢复速度慢。

\2. 数据集大的时候,比 rdb 启动效率低。

\3. 运行效率没有RDB高

总结

AOF文件比RDB更新频率高,优先使用AOF还原数据。

AOF比RDB更安全也更大

RDB性能比AOF好

如果两个都配了优先加载AOF

Redis的过期键的删除策略

Redis是key-value数据库,我们可以设置Redis中缓存的key的过期时间。Redis的过期策略就是指当

Redis中缓存的key过期了,Redis如何处理。

惰性过期:

只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化 地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而 不会被清除,占用大量内存。

定期过期:

每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其 中已过期的

key。该策略是一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时, 可以在不同情况下使得CPU和内存资源达到最优的平衡效果。

Redis线程模型、单线程快的原因

Redis基于Reactor模式开发了网络事件处理器,这个处理器叫做文件事件处理器 file event handler。这个文件事件处理器,它是单线程的,所以 Redis 才叫做单线程的模型,它采用IO多路复用机制来同时监听多个Socket,根据Socket上的事件类型来选择对应的事件处理器来处理这个事件。可以实现高性 能 的网络通信模型,又可以跟内部其他单线程的模块进行对接,保证了 Redis 内部的线程模型的简单性。

文件事件处理器的结构包含4个部分:多个Socket、IO多路复用程序、文件事件分派器以及事件处理器

(命令请求处理器、命令回复处理器、连接应答处理器等)。

多个 Socket 可能并发的产生不同的操作,每个操作对应不同的文件事件,但是IO多路复用程序会监听多个 Socket,会将 Socket 放入一个队列中排队,每次从队列中取出一个 Socket 给事件分派器,事件分派器把 Socket 给对应的事件处理器。然后一个 Socket 的事件处理完之后,IO多路复用程序才会将队列中的下一个 Socket 给事件分派器。文 件事件分派器会根据每个 Socket 当前产生的事件,来选择对应的事件处理器来处理。

单线程快的原因:

纯内存操作

核心是基于非阻塞的IO多路复用机制

单线程反而避免了多线程的频繁上下文切换带来的性能问题

简述Redis事务实现

1. 事务开始

MULTI命令的执行,标识着一个事务的开始。MULTI命令会将客户端状态的 flags 属性中打开

REDIS_MULTI 标识来完成的。

2. 命令入队

当一个客户端切换到事务状态之后,服务器会根据这个客户端发送来的命令来执行不同的操作。如 果客户端发送的命令为MULTIEXECWATCHDISCARD中的一个,立即执行这个命令,否则将命 令放入一个事务队列里面,然后向客户端返回 QUEUED 回复

[如果客户端发送的命令为 EXEC、DISCARD、WATCH、MULTI 四个命令的其中一个,那么服务器立即执行这个命令

如果客户端发送的是四个命令以外的其他命令,那么服务器并不立即执行这个命令。

首先检查此命令的格式是否正确,如果不正确,服务器会在客户端状态(redisClient)的

flags 属性关闭 REDIS_MULTI 标识,并且返回错误信息给客户端。如果正确,将这个命令放入一个事务队列里面,然后向客户端返回 QUEUED 回复

3. 事务执行

客户端发送 EXEC 命令,服务器执行 EXEC 命令逻辑

如果客户端状态的 flags 属性不包含 REDIS_MULTI 标识,或者包含 REDIS_DIRTY_CAS 或者

REDIS_DIRTY_EXEC 标识,那么就直接取消事务的执行。否则客户端处于事务状态(flags 有REDIS_MULTI 标识),服务器会遍历客户端的事务队列,然后执行事务队列中的所有命令, 最后将返回结果全部返回给客户端;

Redis 事务不支持检查那些程序员自己逻辑错误。例如对 String 类型的数据库键执行对 HashMap

类型的操作!

WATCH 命令是一个乐观锁,可以为 Redis 事务提供 check-and-set (CAS)行为。可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续 到EXEC命令。

MULTI命令用于开启一个事务,它总是返回OK。MULTI执行之后,客户端可以继续向服务器 发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC命令被调用时,所有队列中的命令才会被执行。 EXEC:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。当操作被打断时,返回空值 nil 。

通过调用DISCARD,客户端可以清空事务队列,并放弃执行事务, 并且客户端会从事务状态中 退 出 。 UNWATCH命令可以取消watch对所有key的监控

Redis集群方案

主从复制哨兵模式:

sentinel,哨兵是 redis 集群中非常重要的一个组件,主要有以下功能:

集群监控:负责监控 redis master 和 slave 进程是否正常工作。

消息通知:如果某个 redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。

配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。

哨兵用于实现 redis 集群的高可用,本身也是分布式的,作为一个哨兵集群去运行,互相协同工作。

故障转移时,判断一个 master node 是否宕机了,需要大部分的哨兵都同意才行,涉及到了分布式选举

即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的哨兵通常需要 3 个实例,来保证自己的健壮性。

哨兵 + redis 主从的部署架构,是不保证数据零丢失的,只能保证 redis 集群的高可用性。对于哨兵 + redis 主从这种复杂的部署架构,尽量在测试环境和生产环境,都进行充足的测试和演练。

Redis Cluster是一种服务端Sharding技术,3.0版本开始正式提供。采用slot(槽)的概念,一共分成

16384个槽。将请求发送到任意节点,接收到请求的节点会将查询请求发送到正确的节点上执行

方案说明:

通过哈希的方式,将数据分片,每个节点均分存储一定哈希槽(哈希值)区间的数据,默认分配了

16384 个槽位

每份数据分片会存储在多个互为主从的多节点上

数据写入先写主节点,再同步到从节点(支持配置为阻塞同步) 同一分片多个节点间的数据不保持强一致性

读取数据时,当客户端操作的key没有分配在该节点上时,redis会返回转向指令,指向正确的节点扩容时需要需要把旧节点的数据迁移一部分到新节点

在 redis cluster 架构下,每个 redis 要放开两个端口号,比如一个是 6379,另外一个就是 加1w 的端口号,比如 16379。

16379 端口号是用来进行节点间通信的,也就是 cluster bus 的通信,用来进行故障检测、配置更新、故障转移授权。cluster bus 用了另外一种二进制的协议,gossip 协议,用于节点间进行高效的数据交换,占用更少的网络带宽和处理时间。

优点

无中心架构,支持动态扩容,对业务透明

具备Sentinel的监控和自动Failover(故障转移)能力

客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可高性能,客户端直连redis服务,免去了proxy代理的损耗

缺点

运维也很复杂,数据迁移需要人工干预只能使用0号数据库

不支持批量操作(pipeline管道操作) 分布式逻辑和存储模块耦合等

Redis主从复制的核心原理

通过执行slaveof命令或设置slaveof选项,让一个服务器去复制另一个服务器的数据。主数据库可以进行 读写操作,当写操作导致数据变化时会自动将数据同步给从数据库。而从数据库一般是只读的,并接受 主数据库同步过来的数据。一个主数据库可以拥有多个从数据库,而一个从数据库只能拥有一个主数据 库。

全量复制:

\1. 主节点通过bgsave命令fork子进程进行RDB持久化,该过程是非常消耗CPU、内存(页表复制)、硬 盘IO的

\2. 主节点通过网络将RDB文件发送给从节点,对主从节点的带宽都会带来很大的消耗

\3. 从节点清空老数据、载入新RDB文件的过程是阻塞的,无法响应客户端的命令;如果从节点执行

bgrewriteaof,也会带来额外的消耗

部分复制:

\1. 复制偏移量:执行复制的双方,主从节点,分别会维护一个复制偏移量offset

\2. 复制积压缓冲区:主节点内部维护了一个固定长度的、先进先出(FIFO)队列 作为复制积压缓冲区, 当主从节点offset的差距过大超过缓冲区长度时,将无法执行部分复制,只能执行全量复制。

\3. 服务器运行ID(runid):每个Redis节点,都有其运行ID,运行ID由节点在启动时自动生成,主节点 会将自己的运行ID发送给从节点,从节点会将主节点的运行ID存起来。 从节点Redis断开重连的时候,就是根据运行ID来判断同步的进度:

\1. 如果从节点保存的runid与主节点现在的runid相同,说明主从节点之前同步过,主节点会继续尝试使用部分复制(到底能不能部分复制还要看offset和复制积压缓冲区的情况);

\2. 如果从节点保存的runid与主节点现在的runid不同,说明从节点在断线前同步的Redis节点并不是当前的主节点,只能进行全量复制。

缓存雪崩、缓存穿透、缓存击穿

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

解决方案:

缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。 给每一个缓存数据增加相应的缓存标记,记录缓存是否失效,如果缓存标记失效,则更新数据缓存。缓 存预热 互斥锁

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

解决方案

接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截; 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有 效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击 用户 反复用同一个id暴力攻击

采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据 会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力

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

解决方案:

设置热点数据永远不过期。 加互斥锁

一个可用节点即可高性能,客户端直连redis服务,免去了proxy代理的损耗

缺点

运维也很复杂,数据迁移需要人工干预只能使用0号数据库

不支持批量操作(pipeline管道操作) 分布式逻辑和存储模块耦合等

Redis主从复制的核心原理

通过执行slaveof命令或设置slaveof选项,让一个服务器去复制另一个服务器的数据。主数据库可以进行 读写操作,当写操作导致数据变化时会自动将数据同步给从数据库。而从数据库一般是只读的,并接受 主数据库同步过来的数据。一个主数据库可以拥有多个从数据库,而一个从数据库只能拥有一个主数据 库。

全量复制:

\1. 主节点通过bgsave命令fork子进程进行RDB持久化,该过程是非常消耗CPU、内存(页表复制)、硬 盘IO的

\2. 主节点通过网络将RDB文件发送给从节点,对主从节点的带宽都会带来很大的消耗

\3. 从节点清空老数据、载入新RDB文件的过程是阻塞的,无法响应客户端的命令;如果从节点执行

bgrewriteaof,也会带来额外的消耗

部分复制:

\1. 复制偏移量:执行复制的双方,主从节点,分别会维护一个复制偏移量offset

\2. 复制积压缓冲区:主节点内部维护了一个固定长度的、先进先出(FIFO)队列 作为复制积压缓冲区, 当主从节点offset的差距过大超过缓冲区长度时,将无法执行部分复制,只能执行全量复制。

\3. 服务器运行ID(runid):每个Redis节点,都有其运行ID,运行ID由节点在启动时自动生成,主节点 会将自己的运行ID发送给从节点,从节点会将主节点的运行ID存起来。 从节点Redis断开重连的时候,就是根据运行ID来判断同步的进度:

\1. 如果从节点保存的runid与主节点现在的runid相同,说明主从节点之前同步过,主节点会继续尝试使用部分复制(到底能不能部分复制还要看offset和复制积压缓冲区的情况);

\2. 如果从节点保存的runid与主节点现在的runid不同,说明从节点在断线前同步的Redis节点并不是当前的主节点,只能进行全量复制。

缓存雪崩、缓存穿透、缓存击穿

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

解决方案:

缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。 给每一个缓存数据增加相应的缓存标记,记录缓存是否失效,如果缓存标记失效,则更新数据缓存。缓 存预热 互斥锁

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

解决方案

接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截; 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有 效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击 用户 反复用同一个id暴力攻击

采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据 会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力

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

解决方案:

设置热点数据永远不过期。 加互斥锁

创作不易,请关注我哦!!!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值