1.当符号左边是false时,&继续执行符号右边的运算。&&不再执行符号右边的运算。
2.当符号左边是true时,|继续执行符号右边的运算,而||不再执行符号右边的运算
fianl修饰类时,类不能被继承
fianl修饰方法时,方法不能被重写
fianl修饰变量时,变量不能被修改。他就变成常量了
被static所修饰的是静态变量,静态变量由多个对象共享,如果修改了静态变量的值,那么其他对象的静态变量也会被修改
static修饰的方法称为静态方法,可以类名.方法名直接进行调用,不用依赖对象实例也可以进行调用。因为static方法独立于任何实例,因此static方法必须被实现,所以不能是抽象的abstract方法
被static修饰的代码块称为静态代码块,会随着java加载类的时候加载这些代码块。他们可以有多个。可以放在类中的任何位置,但是只能初始化一次
this表示当前对象的引用
super表示父类对象的引用
作用:调用父类的方法、属性、构造器
面向对象三大特征:封装、继承、多态
封装: 通常认为,封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口,封装就是隐藏一切可隐藏的东西,只向外界提供简单的接口
继承: 继承是从已有的类中继承信息创建新类的过程,提供继承信息的类是父类,得到继承信息的类是子类。
多态:多态是指允许不同子类型的对象对同一消息做出不同的响应。(同样的对象调用同样的方法做出了不同分响应)多态分为编译时多态和运行时多态。多态的前提是继承,方法的重写,向上造型
在子类new对象的时候会先调用父类的构造方法,然后再调用子类的构造方法
面向过程就是分析出解决问题所需要的步骤,然后使用函数把这些步骤一步一步的实现,使用的时候一个一个的调用即可。
面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描述某个事物在整个解决问题的步骤中的行为。
所谓的跨平台性,是指Java语言编写的程序,一次编译之后可以在多个平台上运行。
原理:只需要在要运行Java代码的系统上安装一个JVM即可。由JVM负责Java程序在该系统中的运行。所以Java可以跨平台
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D8Wy8heT-1628644245458)(file:///F:/%E6%A1%8C%E9%9D%A2/Gaoven/%E8%AF%BE%E4%BB%B6/Java%E5%9F%BA%E7%A1%80/01-05.files/image006.jpg)]
break:结束当前循环
continue:跳出本次循环,继续执行下次循环
return:程序返回,不再执行下面的代码
这两个都是自增的操作
i++ 是先使用后自增
++i 是先自增在使用
比如:
int i=1;
int j=i++;
return j;
j=1
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
int i=1;
int j=++i;
return j;
j=2
JVM=java虚拟机
JRE=Java虚拟机+核心类库 ----------- 是运行Java程序的环境
JDK=Java虚拟机+核心类库 +编译工具
Java中的基本数据类型:(八大基本类型)
整型 : byte、short、int、long
浮点型 : float、double
字符型:char
布尔型:boolean
所占字节数:
byte :1字节
short: 2字节
int : 4字节
long : 8字节
float : 4字节
double: 8字节
char: 2字节
boolean: 1字节
静态变量前要加static 关键字,而实例变量前则不加。
实例变量是某个对象的属性,必须创建了实例对象,其中的实例变量才会被分配空间,才能使用这个实例变量。
静态变量不属于某个实例对象,而是属于类,所以也称为类变量。
实例变量必须创建对象后才可以通过这个对象来使用,静态变量则可以直接使用类名来引用。
abstract class称为抽象类。
什么是抽象类?
我们把用abstract修饰的方法,称为抽象方法。抽象方法没有方法体。而我们把含有抽象方法的类称为抽象类,必须用abstract修饰,这就是 abstract class
interface称为接口
什么是接口?
接口就是特殊的抽象类,里面所有的方法都是抽象方法,没有一般方法。但是在jdk1.8版本后推出了接口中也可以有一般方法,需要用default ,static修饰
二者的区别:
方法的重载和重写都是多态的实现方式,区别在于重载是编译时多态,重写是运行时多态
重载发生在同一个类中,同名的方法有着不同的参数列表被视为重载
重写发生在子类与父类之间,重写要求子类重写方法与父类的被重写 方法有相同的返回值类型,访问权限大于父类方法
区别:
1.参数列表
重写要求参数列表与被重写方法完全一致
重载要求参数列表中参数的顺序、类型、个数完全不同
2.访问权限,方法的修饰符
重写要求方法权限大于等于被重写方法
重载要求方法名相同即可,没有权限限制
3.异常
重写不能抛出父类没有的一般异常
只能抛出非运行时异常
4.前提
重写的前提是继承,有父子级关系
重载没有前提
== : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象(基本数据类型比较的是值,引用数据类型比较的是内存地址)。
equals() : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:
情况1:类没有覆盖写 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象。
情况2:类override了 equals() 方法。一般,我们都覆盖 equals() 方法来比较两个对象的内容是否相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。
throw 表示抛出一个异常类的对象,生成异常对象的过程。声明在方法体内。
throws 属于异常处理的一种方式,声明在方法的声明处。
clloections是java.util下面的类,它的里面有各种相关集合操作的方法
collection是接口,是各种集合结构的父接口。
不能被继承,因为String类是由fianl修饰的,final修饰的类不能被继承
1.为了实现常量池,节约内存
String有专门的字符串常量池,只有用fianl修饰才能实现常量池。
常量池的存在,没有使用new关键字而产生的String对象,会存储到字符串常量池,当然如果有同样的内容的字符串产生时,将不会再新建对象,直接从常量池中取出来节省了堆内存空间。因为jvm里面,堆是用来存放对象实例的地方。
2.多线程安全
String被fianl修饰后是多线程安全的。同一字符串可以被多个线程共享,多个线程无法改变字符串内容。这就避免了当多个对象指向同一String时如果有一个对象改变了String会对其他的对象产生影响。所以安全。
3.实现String可以创建hashcode不可变性
因为字符串是不可变的,因为他在创建的时候hashcode就被缓存了,不需要重新计算,这就使得字符串很适合做map中的key,字符串的处理速度要快过其他键对象,所有hashmap中往往使用string作为键
A a=new A();
new对象的过程:程序执行到new操作符的时候会看new操作符后面的类型,因为知道了类型才知道分配多大的内存空间。分配完内存空间再调用构造函数填充对象的各个域进行对象的初始化。构造方法返回后,一个对象创建完毕,可以把她的引用地址发送到外部,外部使用这个引用操作对象。
A a1=(A)a.clone()
克隆的第一步与new相似都是分配内存空间。调用克隆方法时分配内存空间与原对象相同,然后使用原对象的各个域填充克隆对象的各个域。clone()返回,一个新的对象就被创建了,内容相同,地址值不同。
重写Comparable接口和Comparator中的方法
Integer.class在装载(Java虚拟机启动)时,其内部类型IntegerCache的static块即开始执行,
实例化并暂存数值在-128到127之间的Integer类型对象。
当自动装箱int型值在-128到127之间时,即直接返回IntegerCache中暂存的Integer类型对象。
-128-127在缓存里取。否则就返回一个新对象。对象的地址值就不相同了,一个在静态区,一个在对象区
浅拷贝(Shallow Copy):指向被复制的内存地址,如果地址发生改变,浅拷贝出来的对象也会相应改变。
深拷贝:为对象都开辟了新的内存空间地址,使这个增加的指针指向新的内存
总结:实则浅拷贝和深拷贝只是相对的,如果一个对象内部只有基本数据类型,那用 clone() 方法获取到的就是这个对象的深拷贝,而如果其内部还有引用数据类型,那用 clone() 方法就是一次浅拷贝的操作。
作用:在网络上传输对象字节序列,把对象字节保存到硬盘上。减少内存消耗。或者在远程rpc调用对象的时候需要。
序列化就是把对象存储到硬盘上,反序列化就是获取对象文件中的数据,转成后台对象类的数据。
操作字符串的类有:String、StringBuffer、StringBuilder。
String 和 StringBuffer、StringBuilder 的区别在于 String 声明的是不可变的对象,每次操作都会生成新的 String 对象,然后将指针指向新的 String 对象,而 StringBuffer、StringBuilder 可以在原有对象的基础上进行操作,所以在经常改变字符串内容的情况下最好不要使用 String。
StringBuffer 和 StringBuilder 最大的区别在于,StringBuffer 是线程安全的,而 StringBuilder 是非线程安全的,但 StringBuilder 的性能却高于 StringBuffer,所以在单线程环境下推荐使用 StringBuilder,多线程环境下推荐使用 StringBuffer。
不一样,因为内存的分配方式不一样。String str="i"的方式,java 虚拟机会将其分配到常量池中;而 String str=new String(“i”) 则会被分到堆内存中。
java文件通过编译器变成了.class文件,接下来类加载器又将这些.class文件加载到JVM中。
其实可以一句话来解释:类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个 java.lang.Class对象,用来封装类在方法区内的数据结构。
1、堆
JVM中最大的一块,主要用来存放对象实例和数组,几乎所有的对象实例都在这里分配内存。线程共享,内部会划分出多个线程私有的分配缓冲区(TLAB)。可以位于物理上不连续的空间,但是逻辑上要连续。
2、虚拟机栈
每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。线程私有,生命周期和线程一致。
3、方法区(非堆)
属于共享内存区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
4、本地方法栈
本地方法栈(Native MethodStacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native 方法服务。
5、程序计数器
程序计数器(Program CounterRegister)是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。此内存区域是唯一一个在Java 虚拟机规范中没有规定任何 OutOfMemoryError 情况的区域
1)物理地址
堆的物理地址分配对对象是不连续的。因此性能慢些。在GC的时候也要考虑到不连续的分配,
所以有各种算法。比如,标记-消除,复制,标记-整理,分代(即新生代使用复制算法,老年
代使用标记——压缩)
栈使用的是数据结构中的栈,先进后出的原则,物理地址分配是连续的。所以性能快。
2)内存分配
堆因为是不连续的,所以分配的内存是在运行期确认的,因此大小不固定。一般堆大小远远大
于栈。
栈是连续的,所以分配的内存大小要在编译期就确认,大小是固定的。
3)存放的内容
堆存放的是对象的实例和数组。因此该区更关注的是数据的存储
栈存放:局部变量,操作数栈,返回结果。栈区更关注的是程序方法的执行。
4)程序的可见度
堆对于整个应用程序都是共享、可见的。
栈只对于线程是可见的。所以也是线程私有。他的生命周期和线程相同。
对象引用类型分:强引用、软引用、弱引用和虚引用。
强引用:不会被回收。就是我们一般声明对象是时虚拟机生成的引用,强引用环境下,垃圾回收时需要严格判断当前对象是否被强引用,如果被强引用,则不会被垃圾回收。
软引用:内存充足,则不回收;不足则回收。软引用一般被做为缓存来使用。与强引用的区别是,软引用在垃圾回收时,虚拟机会根据当前系统的剩余内存来决定是否对软引用进行回收。如果剩余内存比较紧张,则虚拟机会回收软引用所引用的空间;如果剩余内存相对富裕,则不会进行回收。换句话说,虚拟机在发生OutOfMemory时,肯定是没有软引用存在的。
弱引用:会被回收。弱引用与软引用类似,都是作为缓存来使用。但与软引用不同,弱引用在进行垃圾回收时,是一定会被回收掉的,因此其生命周期只存在于一个垃圾回收周期内。
主流的有三种垃圾回收算法:复制算法,标记-清除算法、标记-整理算法
永久代(Perm):主要保存class,method,field等对象,该空间大小,取决于系统启动加载类的数量,一般该区域内存溢出均是启动时溢出。java.lang.OutOfMemoryError: PermGen space
老年代(Old):一般是经过多次垃圾回收(GC)没有被回收掉的对象。
伊甸园(Eden):新创建的对象。
幸存区0(Survivor0):经过垃圾回收(GC)后,没有被回收掉的对象。
幸存区1(Survivor1):同Survivor0相同,大小空间也相同,同一时刻Survivor0和Survivor1只有一个在用,一个为空。
首先标记出所有需要回收的对象,标记完成后统一回收所有被标记的对象。缺点:标记和清除两个过程效率都不高;标记清除后会产生空间碎片,空间碎片导致分配较大对象时可能提前触发垃圾回收。
将可用内存分为两个区域,每次只使用其中一块,当使用的那一块内存用完时,将还存活的对象复制到另外一块内存中,然后把已使用过的内存空间一次清理掉。优点:解决的空间碎片问题,实现简单。缺点:需要两倍空间,将内存缩小为两块,内存使用率不高。复制操作频繁效率变低。
此算法结合了“标记-清除”和“复制”两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放。此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。
垃圾回收策略可以看作是内存回收的抽象策略,而垃圾收集器是内存回收的具体实现
垃圾收集器有很多种,常见的有:串行收集器、并行收集器、并发收集器、CMS收集器以及最新的G1收集器。重点为CMS收集器和G1收集器
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器
基于 标记清除 算法实现。第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。
特点:
针对老年代
基于"标记-清除"算法(不进行压缩操作,会产生内存碎片)
以获取最短回收停顿时间为目标
并发收集、低停顿
需要更多的内存
运作步骤:
初始标记: 暂停所有的其他线程,标记GC Roots能直接关联到的对象,速度很快;
并发标记:进行GC Roots Tracing的过程;
重新标记: 修正并发标记期间的变动部分,需要"Stop The World",且停顿时间比初始标记稍长,但远比并发标记短;
并发清除: 开启用户线程,同时GC线程开始对为标记的区域做清扫,回收所有的垃圾对象。
缺点:
对 CPU 资源敏感;
无法收集浮动垃圾;
标记清除 算法带来的空间碎片。
G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器。以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征。
G1是将整个堆空间分成许多个大小不等的独立区域(Region),大约有2000块,每个Region从1M到32M大小不等,在JVM启动的时候就已经分割好了,Region可采用并行的垃圾回收或 NOT STW 方式。
运作步骤:
初始标记(Initial Marking)
并发标记(Concurrent Marking)
最终标记(Final Marking)
筛选回收(Live Data Counting and Evacuation)
特点:
1、并行与并发
G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短stop-The-World停顿时间。部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让java程序继续执行。
2、分代收集
虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但是还是保留了分代的概念。
能独立管理整个GC堆(新生代和老年代),而不需要与其他收集器搭配;
能够采用不同方式处理不同时期的对象;
虽然保留分代概念,但Java堆的内存布局有很大差别;
将整个堆划分为多个大小相等的独立区域(Region);
新生代和老年代不再是物理隔离,它们都是一部分Region(不需要连续)的集合。
3、空间整合
与CMS的“标记–清理”算法不同,G1从整体来看是基于“标记整理”算法实现的收集器;从局部上来看是基于“复制”算法实现的。是一种类似火车算法的实现,不会产生内存碎片,有利于长时间运行。
4、可预测停顿
这是G1相对于CMS的另一个大优势,降低停顿时间是G1和CMS共同的关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型。可以明确指定M毫秒时间片内,垃圾收集消耗的时间不超过N毫秒。在低停顿的同时实现高吞吐量。
为什么G1可以实现可预测停顿?
可以有计划地避免在Java堆的进行全区域的垃圾收集;
G1收集器将内存分大小相等的独立区域(Region),新生代和老年代概念保留,但是已经不再物理隔离。
G1跟踪各个Region获得其收集价值大小,在后台维护一个优先列表;
每次根据允许的收集时间,优先回收价值最大的Region(名称Garbage-First的由来);
类加载器实现的功能是:为加载阶段获取二进制字节流
1、加载
加载主要是将.class文件(并不一定是.class。可以是ZIP包,网络中获取)中的二进制字节流读入到JVM中。
在加载阶段,JVM需要完成3件事:
通过类的全限定名获取该类的二进制字节流;
将字节流所代表的静态存储结构转化为方法区的运行时数据结构;
在内存中生成一个该类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
2、连接
2.1 验证
验证阶段,确保加载进来的字节流符合JVM规范。将完成以下4个阶段的检验动作:
文件格式验证
元数据验证(是否符合Java语言规范)
字节码验证(确定程序语义合法,符合逻辑)
符号引用验证(确保下一步的解析能正常执行)
2.2 准备
准备阶段,为静态变量在方法区分配内存,并设置默认初始值。
2.3 解析
解析阶段,是虚拟机将常量池内的符号引用替换为直接引用的过程。
3、初始化
初始化阶段是类加载过程的最后一步,主要是根据程序中的赋值语句主动为类变量赋值。
(注:当有父类且父类为初始化的时候,先去初始化父类;再进行子类初始化语句)
类加载器之间的这种层次关系叫做双亲委派模型。
双亲委派模型要求除了顶层的启动类加载器(Bootstrap ClassLoader)外,其余的类加载器都应当有自己的父类加载器。这里的类加载器之间的父子关系一般不是以继承关系实现的,而是用组合实现的。
如果一个类接受到类加载请求,他自己不会去加载这个请求,而是将这个类加载请求委派给父类加载器,这样一层一层传送,直到到达启动类加载器(Bootstrap ClassLoader)。
只有当父类加载器无法加载这个请求时,子加载器才会尝试自己去加载。
为何要采用双亲委派机制呢?了解为何之前,我们先来说明一个知识点:判断两个类相同的前提是这两个类都是同一个加载器进行加载的,如果使用不同的类加载器进行加载同一个类,也会有不同的结果。
如果没有双亲委派机制,会出现什么样的结果呢?比如我们在rt.jar中随便找一个类,如java.util.HashMap,那么我们同样也可以写一个一样的类,也叫java.util.HashMap存放在我们自己的路径下(ClassPath).那样这两个相同的类采用的是不同的类加载器,系统中就会出现两个不同的HashMap类,这样引用程序就会出现一片混乱。
通过一个空参的构造器创建对象时1.7底层创建了长度是10的数组。当我们向集合中添加第11个元素时,底层会进行扩容,扩容为原来数组长度的1.5倍。同时将原数组中的内容复制新的数组中。 元素要求所在的类必须重写equals方法(饿汉式)
1.8中并没有直接就创建长度为10的数组,调用add的时候才创建。(懒汉式)
有List 和 set
list下面有
arrayList :底层是数组,线程不安全,查找快增删慢
LinkedList:底层是双向链表。查找慢,增删插入快
Vector:底层是object数组,线程安全效率低
set下面有
HashSet :是Set的主要实现类,线程不安全,底层是hashMap。初始容量和加载因子都和hashMap一样。
treeSet:可以按照对象的指定属性进行排序
LinkedHashSet:是按照添加元素的顺序进行遍历,因为底层维护了一张链表用来记录添加的顺序
Map分为hashmap和linkedhashmap,hashtable,treemap,
HashMap:是线程不安全的,可以存放null的key和vaule,如果是null默认从第0个查找。
LinkedHashMap:和Hashmap一样,只是可以按照元素添加的顺序进行遍历。底层用双向链表来记录元素添加顺序。
HashTable:是线程安全的。不可能存null。但是Hashtable是表级锁底层方法都是使用synchronized来保证线程安全,而ConcurrentHashMap是段级锁
TreeMap:添加的key-vaule对进行排序,可以考虑订制排序和自然排序,实现Comparator和 Comparable接口。
LinkeList 和 ArrayList 都实现了List接口,但是LinkeList还额外实现了Deque接口。
LinkeList 底层是链表,因为实现的Deque接口,所以她还是一个双端队列。链表的结构支持她高效的插入和删除。对查找方法很低效。但是实现的Deque接口的双端队列会一直维护她的头指针和尾指针,所以她对于头部和尾部的查找还是很高效的。
ArrayList和LinkedList都是线程不安全的
底层数据结构不同,ArrayList 是数组,LinkedList 是链表
插入和删除受影响。ArrayList 采用数组存储插入删除的时间复杂度受位置的影响近似为 O(n)。
LinkeList采用链表结构,插入删除不受位置影响,时间复杂度为O(1)
ArrayList支持快速访问随机位置,LinkedList不支持高效访问
内存空间占用问题:ArrayList空间浪费,分配的内存空间即使用了一点点,剩下的空间也会占着。
hashcode() 和 equals()…hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)。
当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相同的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象。如果equals方法返回为true,HashSet 就不会让其加入操作成功。如果返回false,就会重新散列到其他位置。
在Java1.8之前 HashMap底层是 数组+链表
在Java1.8之后 HashMap底层是 数组+链表+红黑树。链表长度超过8的时候链表转化成红黑树
Key
对应的hash
值,再计算其数组下标;TREEIFYTHRESHOLD==8
),就把链表转成红黑树,链表长度低于6,就把红黑树转回链表;key
已经存在,则替换其value
即可;resize
方法进行数组扩容。因为链表长了就会查询慢的问题,红黑树相当于排序数据。可以自动的使用二分法进行定位。性能较高。
红黑树是一种平衡二叉查找树,他的节点是红色或者黑色,根节点是黑色,每个叶子节点都是黑色的空节点,红色的节点他的子节点都是黑色,他可以用变色和旋转来调整平衡
1)开放定址法:当冲突发生时,使用某种探查(亦称探测)技术在散列表中形成一个探查(测)序列。沿此序列逐个单元地查找,直到找到给定 的关键字,或者碰到一个开放的地址(即该地址单元为空)为止(若要插入,在探查到开放的地址,则可将待插入的新结点存人该地址单元)。查找时探查到开放的 地址则表明表中无待查的关键字,即查找失败。
2) 再哈希法:同时构造多个不同的哈希函数。
3)链地址法:将所有哈希地址为i的元素构成一个称为同义词链的单链表,并将单链表的头指针存在哈希表的第i个单元中,因而查找、插入和删除主要在同义词链中进行。链地址法适用于经常进行插入和删除的情况。
4)建立公共溢出区:将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表。
之所以选择红黑树是为了解决二叉查找树的缺陷,二叉查找树在特殊情况下会变成一条线性结构(这就跟原来使用链表结构一样了,造成很深的问题),遍历查找会非常慢。而红黑树在插入新数据后可能需要通过左旋,右旋、变色这些操作来保持平衡,引入红黑树就是为了查找数据快,解决链表查询深度的问题,我们知道红黑树属于平衡二叉树,但是为了保持"平衡"是需要付出代价的,但是该代价所损耗的资源要比遍历线性链表要少,所以当长度大于8的时候,会使用红黑树,如果链表长度很短的话,根本不需要引入红黑树,引入反而会慢。
JDK1.8
中,是通过hashCode()
的高16位异或低16位实现的:(h=k.hashCode())^(h>>>16)
,主要是从速度,功效和质量来考虑的,减少系统的开销,也不会造成因为高位没有参与下标的计算,从而引起的碰撞。
HashMap
是线程不安全的,HashTable
是线程安全的;HashTable
的效率比不上HashMap
;HashMap
最多只允许一条记录的键为null
,允许多条记录的值为null
,而HashTable
不允许;HashMap
默认初始化数组的大小为16,HashTable
为11,前者扩容时,扩大两倍,后者扩大两倍+1;HashMap
需要重新计算hash值,而HashTable
直接使用对象的hashCode
;ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同。
Java中的Iterator功能比较简单,并且只能单向移动:
(1) 使用方法iterator()要求容器返回一个Iterator。第一次调用Iterator的next()方法时,它返回序列的第一个元素。注意:iterator()方法是java.lang.Iterable接口,被Collection继承。
(2) 使用next()获得序列中的下一个元素。
(3) 使用hasNext()检查序列中是否还有元素。
(4) 使用remove()将迭代器新返回的元素删除。
Iterator是Java迭代器最简单的实现,为List设计的ListIterator具有更多的功能,它可以从两个方向遍历List,也可以从List中插入和删除元素。
线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。
线程是进程的子集,一个进程可以有很多线程,每条线程并行执行不同的任务。
不同的进程使用不同的内存空间,而所有的线程共享一片相同的内存空间
创建状态(New):用三种方式创建线程,还没有调用start。
就绪状态(Runnable):在创建了线程之后,调用Thread类的start()方法来启动一个线程,即表示线程进入就绪状态。
运行状态(Running):当线程获得CPU时间,线程才从就绪状态进入到运行状态
阻塞状态(Blocked):运行中的线程进入阻塞状态,如调用sleep()方法让线程睡眠,调用wait()方法让线程等待等。
终止状态(Dead):当线程的run()方法完成时,或者主线程的main()方法完成时,我们就认为它终止了。
大家都知道我们可以通过继承Thread类或者调用Runnable接口来实现线程,问题是,那个方法更好呢?什么情况下使 用它?这个问题很容易回答,如果你知道Java不支持类的多重继承,但允许你调用多个接口。所以如果你要继承其他类,当然是调用Runnable接口好了。
start()方法被用来启动新创建的线程,而且start()内部调用了run()方法,这和直接调用run()方法的效果不一样。当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启 动,start()方法才会启动新线程
Runnable和Callable都代表那些要在不同的线程中执行的任务。Runnable从JDK1.0开始就有了,Callable是在 JDK1.5增加的。它们的主要区别是Callable的 call() 方法可以返回值(Future对象)和抛出异常,而Runnable的run()方法没有这些功能。Callable可以返回装载有计算结果的Future对象。
创建线程要花费昂贵的资源和时间,如果任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程数有限。为了避免这些问题,在程序启动的时 候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程。从JDK1.5开始,Java API提供了Executor框架让你可以创建不同的线程池。比如单线程池,每次处理一个任务;数目固定的线程池或者是缓存线程池
第一个参数为corePoolSize 核心线程数
第二个参数为maximumPoolSize 最大线程数
第三个参数为keepAliveTime 线程的保存时间
线程失效时的回调函数
notify()方法不能唤醒某个具体的线程,所以只有一个线程在等 待的时候它才有用武之地。而notifyAll()唤醒所有线程并允许他们争夺锁确保了至少有一个线程能继续运行。
当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。
死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。这是一个严重的问题,因为死锁会让你的程序挂起无法完成任务,死锁的发生必须满足以下四个条件:
互斥条件:一个资源每次只能被一个进程使用。
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
避免死锁最简单的方法就是阻止循环等待条件,将系统中所有的资源设置标志位、排序,规定所有的进程申请资源必须以一定的顺序(升序或降序)做操作来避免死锁。
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量 的值也和预期的是一样的,就是线程安全的。
在介绍多线程的时候,我们首先要知道什么是线程,而要了解线程还要了解进程。
1.进程:一个正在执行中的程序,每个进程执行都有一个执行顺序
2.线程:进程中的一个独立控制单元,线程在控制进程的执行。一个进程中至少有一个线程。
3.多线程:一个进程中不只有一个线程。
总是假设最坏的情况,每次拿数据的时候都会认为别人会修改,所以在每次拿数据的时候都会上锁,这样别人想要拿这个数据的时候就会因为加锁了而阻塞,直到他拿到锁。
悲观锁核心思想:共享资源每次只给一个线程使用,其他进程阻塞,用完后再把资源转让给其他的进程。
关系型数据库很多都用到了悲观锁机制,Java中synchronized 和 ReetrontLock 是悲观锁
总是假设最好的情况,每次拿数据的时候都会认为别人不会修改,所以不会上锁。但是在更新的时候回先判断一下
别人有没有更新这个数据,可以使用 版本号控制机制和CAS算法实现,乐观锁适用于多读的应用类型,这样可以提高吞吐量。
乐观锁适用于少写多读的情况,多读冲突少可以省去锁的开销提高吞吐量,多写的情况使用悲观锁比较安全
一般在数据表中加上一个数据版本号的字段version,表示数据被修改的次数,当数据被修改的时候version+1,当数据更新的时候,读取数据的同时也会读取版本号,更新数据时,若当前读到的version和数据库中存储的version一致的时候说明,在更新期间没有别的线程操作此数据,此时允许更新。如果version不一致,说明更新期间数据被其他线程修改了,此时不允许更新。
1.sleep不会释放对象锁。Wait会释放锁
2.sleep会自动唤醒。wait需要其它线程唤醒。
3.sleep是Thread中的方法。wait是Object中的方法。
4.sleep可以在任何地方使用,而wait只能在同步方法或者同步块中使用。
定义:
公平锁是指多个线程按照申请锁的顺序来获得锁,线程直接进入队列中排队,队列中的第一个线程才能获得锁
优点
等待锁的线程不会饿死。
缺点
整体吞吐效率比非公平锁要低,等待队列中除第一个以外的线程都会阻塞,CPU唤醒阻塞线程的开销比非公平锁要大。
公平锁排队的理解:如图,新来了一个D,D要进行排队,排到队列的最后面,最后来的最后获得锁
概念
非公平锁是多个线程加锁时直接尝试获得锁,获取不到才会进入等待队列的队尾等待。但如果此时锁刚好可用,那么这个线程可以无需阻塞直接获取到锁,所以非公平锁有可能出现后申请锁的线程先获得锁的场景。
优点
可以减少唤醒线程的开销,整体吞吐效率高,因为线程有几率不阻塞直接获得锁。
缺点
处于等待队列中的线程可能会饿死,或者等很久才会获得锁。
运行机制如下图:右上角此时锁刚好可用,那么这个线程可以无需阻塞直接获取到锁
右下角获取不到会进入等待队列的队尾等待
锁一般的分类有:同步锁、互斥锁、悲观锁、乐观锁、公平锁、非公平锁。
java中,一把锁可能同时占有多个标准,符合多种分类
偏向锁/轻量级锁/重量级锁:这三种锁特指synchronized锁的状态
它的思想是如果自始至终,对于这把锁都不存在竞争,那么其实就没必要上锁,只要打个标记就行了。有点类似于乐观锁的机制,乐观锁就是默认不竞争,没必要上锁。但是他不是乐观锁。。
偏向锁概念:
一个对象在被初始化后,如果还没有任何线程来获取它的锁时,它就是可偏向的,当有第一个线程来访问它尝试获取锁的时候,它就记录下来这个线程,如果后面尝试获取锁的线程正是这个偏向锁的拥有者,就可以直接获取锁,开销很小。
JVM 的开发者发现在很多情况下,synchronized 中的代码块是被多个线程交替执行的,也就是说,并不存在实际的竞争,或者是只有短时间的锁竞争,用 CAS 就可以解决
偏向锁升级到轻量级锁
这种情况下,重量级锁是没必要的。轻量级锁指当锁原来是偏向锁的时候,被另一个线程所访问,说明存在竞争,那么偏向锁就会升级为轻量级锁,线程会通过自旋的方式尝试获取锁,不会阻塞。
会对拿不到锁的线程进行阻塞
这种锁利用操作系统的同步机制实现,所以开销比较大。当多个线程直接有实际竞争,并且锁竞争时间比较长的时候,
此时偏向锁和轻量级锁都不能满足需求,锁就会膨胀为重量级锁。重量级锁会让其他申请却拿不到锁的线程进入阻塞状态
这篇文章很好
https://blog.csdn.net/qq_43542795/article/details/105970892?ops_request_misc=&request_id=&biz_id=102&utm_term=%E9%94%81%E8%86%A8%E6%B6%A8&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduweb~default-3-.pc_search_result_control_group&spm=1018.2226.3001.4187
锁加在谁身上,用法区别
synchronized 可以自定义monitor锁对象。(加解锁是隐式的)
Lock接口必须用Lock锁对象来加锁和解锁。(显式的加锁,解锁,抛异常时也能保证释放锁)
加解锁顺序不同:
对于 Lock 而言如果有多把 Lock 锁,Lock 可以不完全按照加锁的反序解锁:
但是 synchronized 无法做到,synchronized 解锁的顺序和加锁的顺序必须完全相反
synchronized 锁只能同时被一个线程拥有,但是 Lock 锁没有这个限制
原理区别
是否可以设置公平/非公平
Java BIO: 同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
Java NIO : 同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上 ,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
Java AIO(NIO.2) : 异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。
按功能来分:输入流(input)、输出流(output)。
按类型来分:字节流和字符流。
三次握手:要建立连接时,1客户端发送一个SYN报文给服务器,2服务器收到请求后,向客户端响应一个SYN+ACK连接数据报文,3然后客户端收到报文,回应ACK报文,服务器收的到ACK完成3次握手。
四次挥手:双方关闭连接要经过双方都同意。所以,1首先是客服端给服务器发送FIN,要求关闭连接,2服务器收到后会发送一个ACK进行确认。3服务器关闭连接然后再发送一个FIN,4客户端收到FIN发送ACK确认,并进入TIME_WAIT状态。等待2MSL后自动关闭。服务器收到ACK就关闭连接了。
为什么是3次握手:因为没有收到服务器返回确认报文,这时会放弃连接,重新启动一条连接请求,因为他们之间要相互确认是否收到ACK。
1.基于连接与无连接;
2.对系统资源的要求(TCP较多,UDP少);
3.UDP程序结构较简单;
4.流模式与数据报模式 ;
5.TCP保证数据正确性,UDP可能丢包,TCP保证数据顺序,UDP不保证。
SpringBoot是spring组件一站式解决方案快速整合第三方框架,主要是简化Spring的使用难度,简化了繁琐的配置(他是如何快速整合的呢?其实他是的基本原来是Maven依赖关系,Maven的集成,完全采用注解化,简化XML配置,)提供各种启动器,开发者能快速上手。
1.容易上手,整合其他第三方框架提升开发效率
2.开箱即用远离繁琐的配置
3.提供了一系列的大型通用非业务功能,内嵌HTTP服务器(Tomcate,jetty)
4.集成maven通过注解简化xml配置,避免jar包冲突
.yml 和.properities
他们的主要区别是书写的格式不同,yml格式不支持@PropertySource注解导入配置
@SpringBootApplication是启动类上的最重要的一个注解
@SpringBootConfiguration
@SpringBootConfiguration 其实就是对原 @Configuration 注解的简单封装。
@Bean
用来代替 XML 配置文件里面的bean配置。
@ImportResource
如果有些通过类的注册方式配置不了的,可以通过这个注解引入额外的 XML 配置文件,有些老的配置文件无法通过 @Configuration 方式配置的非常管用。
@Import
用来引入额外的一个或者多个 @Configuration 修饰的配置文件类。
@ComponentScan
@ComponentScan主要用来开启组件扫描,可以自动扫描指定包路径下的@Component注解类并将bean实例注册到context中。
@EnableAutoConfiguration
@EnableAutoConfiguration主要用来提供自动装配,是这三个注解中最重要的一个注解。她是Spring Boot新添加的注解,提供了强大的自动依赖功能
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6Jr4Itr2-1628644245462)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210731103218244.png)]
Spring Boot 的核心配置文件是 application 和 bootstrap 配置文件。
application 配置文件这个容易理解,主要用于 Spring Boot 项目的自动化配置。
bootstrap 配置文件有以下几个应用场景。
使用 Spring Cloud Config 配置中心时,这时需要在 bootstrap 配置文件中添加连接到配置中心的配置属性来加载外部配置中心的配置信息;
一些固定的不能被覆盖的属性;
一些加密/解密的场景;
DispatcherServlet(前端控制器
相当于转发器、中央处理器。DispatcherServlet是整个流程控制的中心
HandlerMapping(处理器映射器
根据请求的url查找Handler。HandlerMapping负责根据用户请求找到Handler即处理器
HandlerAdapter(处理器适配器
按照特定规则(HandlerAdapter要求的规则)去执行Handler。
View resolver(视图解析器
View Resolver负责将处理结果生成View视图
View(视图)
View是一个接口,实现类支持不同的View类型(jsp、freemarker),就是我们真正呈现的用户效果
1.前端用户发送请求到前端控制器,Dispatcher Servlet
2.Dispatcher Servlet收到请求后,调用HandlerMapping去获取Handler,HandlerMapping根据具体的url请求找到具体的Handler交给Dispatcher Servlet。0
3.Dispatcher Servlet调用HadnlerAdapter执行Handler将执行结果返回Dispatcher Servlet
4.Dispatcher Servlet将执行结果ModleAndView交给view resolver进行视图解析,结果返回
5.Dispatcher Servlet对view进行渲染返回给用户
@RequestMapping
用于处理url映射的注解,用在方法和类上。用在类上表示,类中所有的响应请求的方法都是以该类为父路径
@RequestBody
接收http请求的json数据将json转化成Java对象
@ResponseBody
将对象转化成json响应给客户。
@Resource和@Autowired都可以用来装配bean,都可以用于字段或setter方法。
@Autowired默认按类型装配,默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false。
@Resource默认按名称装配,当找不到与名称匹配的bean时才按照类型进行装配。名称可以通过name属性指定,如果没有指定name属性,当注解写在字段上时,默认取字段名,当注解写在setter方法上时,默认取属性名进行装配。
Spring是一个轻量级的IOC和AOP容器框架,目的是用于简化企业应用程序开发,使开发者只关心业务需求
1.Spring是中间层、粘合剂、对其他主流框架进行整合,使得浑然一体
2.spring的IOC、DI机制降低对象之间的耦合性
3.spring的AOP支持将一些通用的任务如:安全、日志、权限等进行管理提高可用性
IOC 和 AOP。
IOC让相互协作的组件保持松散的耦合,
而AOP编程允许把遍布于应用各层的功能分离出来形成可重用的功能组件。
IOC是一种解耦的设计思想
是指控制反转,指创建对象的控制权转交给了spring容器来进行管理控制对象的生命周期。由spring根据配置文件去创建实例和管理实例之间的依赖关系,对象与对象之间实现松耦合
DI依赖注入
和控制反转是同一概念的不同角度,IOC容器动态的将某个对象所需要的资源注入到组件中。
AOP是一种面向切面的编程思想,是对OOP的一种补充。用于将那些与业务无关但却对多个对象产生影响的的公共行为和逻辑抽取并封装成一个可重用的模块,这个,模块叫切面
切面:被抽取出来的公共模块
连接点:执行的目标方法
切点:满足扫描条件的所有连接点的集合
通知:定义切面方法执行的时机
目标对象:被代理的对象。
粗粒度
bean: 按照bean匹配,当前bean中的方法都会执行通知
within 可以匹配多个类
细粒度
execution方法参数级别
@annoction 按照注解匹配
before 执行目标方法之前执行
after 执行目标方法之后执行
around 之前目标方法前后都执行
afterThrowing 出现异常后执行
afterReturning 有返回值后执行
静态代理
Aspect-J静态代理
动态代理
JDK动态代理
cglib动态代理
JDk动态代理只提供接口的代理,不支持类的代理,要求被代理的类必须是实现接口。JDK代理的核心是 InvocationHandler 和proxy。在获取代理对象的时候使用proxy动态创建目标对象的代理类,当代理对象调用真实的方法时,InvacatinHandler调用invoke()方法反射调用目标类的方法
如果被代理的类没有实现接口,那么springAOP会选择使用cglib动态代理目标类,cglib是一个代码生成的类库,可在运行时动态生成一个指定类的子类对象并覆盖其中的特定方法,添加增强代码实现AOP
Aspect-J是静态代理也成为编译时增强,AOP框架会在编译阶段生成AOP代理类,并将AsprctJ(切面)织入java字节码中,运行时就是增强后的AOP对象。
spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会修改字节码,而是每次运行时在内存中临时为方法生成一个Aop对象,这个aop对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法
工厂模式
Spring使用工厂模式可以通过 BeanFactory 或 ApplicationContext 创建 bean 对象
单例模式
Spring 中 Bean 的默认作用域就是 singleton(单例)的
单例模式的好处
对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统 开销;
由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时 间。
代理模式
Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术
模板模式
用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate等。
观察者模式
定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现:ApplicationListener。
1、ApplicationContext 是 BeanFactory 的子接口,功能更全
BeanFactory是Spring里面最底层的接口,包含了各种Bean的定义,读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。
ApplicationContext是BeanFactory的派生接口,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能:
2、加载方式
BeanFactroy 采用的是延迟加载形式来注入Bean,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化
ApplicationContext 是在容器启动时,一次性创建了所有的Bean。
这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。 ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。
相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。
3、创建方式
BeanFactory 通常以编程的方式被创建。
ApplicationContext 除了编程方式,还能以声明的方式创建,如使用ContextLoader。
4、注册方式
BeanFactory需要手动注册,而ApplicationContext则是自动注册。
MyBatis是一款优秀的持久层框架,一个半ORM(对象关系映射)的框架,它支持定制化SQL,存储过程,高级映射
MyBatis底层封装了JDBC,避免了几乎所有的JDBC代码,手动设置参数以及获取结果集
ORM时对象关系映射,是一种为了解决关系型数据库与简单Java对象的映射关系的技术。将程序中的对象自动持久化到关系型数据库
自动与半自动的却别在于用不用手写sql
1.映射关系
MyBatsi是一个半自动对象关系映射的框架
Hibernate是一个全自动对象关系映射的框架
2.SQL优化
Hibernate’对sql进行了封装,优化困难
MyBatis需要手动编写sql,支持动态sql优化简单
3.开发难易,学习成本
Hibernate是重量级框架,学习使用门槛高,适用于需求相对稳定的中小型项目
MyBatis是轻量级框架,学习门槛低。适用于需求变化频繁的互联网大型项目
1.创建SqlSwssionFactory
2.通过SqlSessionFactory创建SqlSession
3.通过SqlSesson执行数据库操作
4.调用session.commit()提交事务
5.调用session.close()关闭会话
#{} 是预编译处理,mybatis在处理#{}的时候,会将sql中的#{}替换为?占位符进行占位,调用预编译语句preparedStatement中的set注入参数,可以有效的防止sql注入攻击。支持基本数据类型(八大数据类型、包装类、BigDecimal)
** 是 字 符 串 的 替 换 。 ∗ ∗ M y B a t i s 在 处 理 {} 是字符串的替换。**MyBatis在处理 是字符串的替换。∗∗MyBatis在处理{}的时候会将${}里面的变量原原本本的赋值到sql里面,不会给他加上单引号会出现sql注入问题。不支持基本类型。
Mybatis动态sql可以让我们在Xml映射文件内,以标签的形式编写动态sql,完成逻辑判断和动态拼接sql的功能,Mybatis提供了9种动态sql标签trim|where|set|foreach|if|choose|when|otherwise|bind。
虽然Mybatis解析Xml映射文件是按照顺序解析的,但是,被引用的B标签依然可以定义在任何地方,Mybatis都可以正确识别。
原理是,Mybatis解析A标签,发现A标签引用了B标签,但是B标签尚未解析到,尚不存在,此时,Mybatis会将A标签标记为未解析状态,然后继续解析余下的标签,包含B标签,待所有标签解析完毕,Mybatis会重新解析那些被标记为未解析的标签,此时再解析A标签时,B标签已经存在,A标签也就可以正常解析完成了。
resultType和resultMap功能类似 ,都是返回对象信息,
区别在于resultMap要手动配置一下,表和类的对应关系。当实体类和数据库表字段一一对应的时候,
resultType会自动建立对应关系;当实体类和数据库表字段不一致的时候,使用resultMap手动建立对应关系。
MyBatis-Plus是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。如果添加了MybatisPlus的依赖 但是不想使用MybatisPlus的类和方法 这时候仍然可以按照之前使用Mybatis的步骤做开发 几乎和之前没有任何的改变
是在Spring Boot 基础上构建的,用于快速构建分布式系统的通用模式的工具集.这些工具集包括(服务注册、服务配置、负载均衡、声明式调用、网关、熔断器、链路监控等)
1、服务注册与发现 Eureka、nacos组件
2、负载均衡 Ribbon组件
3、声明式调用 Feign组件
4、熔断器 Hystrix、sentinel组件
5、路由网关组件 Zuul、GateWay组件
6、 配置中心nacos、Apollo(携程)、spring cloud config组件
7、服务链路追踪 Spring Cloud Sleuth 、zipkin组件
SpringBoot 不依赖于 SpringCloud,SpringCloud 依赖于 SpringBoot,属于依赖关系。
SpringBoot 专注于快速、方便的开发单个的微服务个体,SpringCloud 关注全局的服务治理框架。整合并管理各个微服务,为各个微服务之间提供服务注册与发现、负载均衡、声明式调用、熔断器、路由网关、配置中心、服务链路追踪、微服务监控等集成服务
统一的权限校验
继承 zuulFeliter 过滤器实现过滤
统一的调用入口
集成hystrix
集成ribbon
默认启用负载均衡
默认不开启自动重试
会对后台造成很大的压力
会造成雪崩
注册
客户端一次次反复尝试注册,直到注册成功为止
拉取
客户端每30s拉去一次eureka上面的注册表单,刷新注册表
心跳
客户端每30s发送一次心跳数据
连续三次收不到一个服务的心跳,会删除服务
自我保护
由于网路不稳定,15分钟内,85%的服务出现心跳异常,自动进入自我保护模式,所有的注册信息不删除
等待网络恢复正常后会自动退出保护模式
开发阶段可关闭保护模式10-113
为啥要有配置中心?
每次修改配置都在application.yml文件中修改,修改成功后必须重启服务才会生效,但是我们真正运行的服务如果突然重启的话会对数据产生影响,所以需要一种不用重启项目也能让配置生效的技术,这就是服务配置中心。
服务的配置中心有 Nacos、Spring cloud config
1.RestTemplate
2.LoadBalanceClient
3.@LoadBalance
4.Feign
5.Ribbon
Nginx是一个 轻量级/高性能的反向代理Web服务器,他实现非常高效的反向代理、负载平衡,他可以处理2-3万并发连接数,官方监测能支持5万并发
轮询
upstream backserver {
server 192.168.0.14;
server 192.168.0.15;
}
权重
upstream backserver {
server 192.168.0.14 weight=3;
server 192.168.0.15 weight=7;
}
IPHash
upstream backserver {
ip_hash;
server 192.168.0.14:88;
server 192.168.0.15:80;
}
正向代理
前端的请求交给正向代理,由正向代理向服务器发送请求。对于服务器来讲只会认为他在与代理进行交互,不知道谁发来的请求,保护了用户。
正向代理的好处
反向代理
反向代理是后端服务器不直接对外暴漏,请求首先发送到反向代理,然后由反向代理将请求转发到后端服务器。
阻止web攻击Nginx可做反向代理,也可以做负载均衡分发前端请求。
Nginx采用多进程单线程模式,多路IO复用模型。事件处理机制:异步非阻塞事件处理机制:运用了epoll模型,提供了一个队列,排队解决。
节假日、大型活动,这些场景会引起服务器流量暴涨,导致网页无法显示,App反应慢呢,功能无法正常运转,甚至会引起系统崩溃。
如何在这些业务在流量变化无常的情况下保持各种业务安全运行,系统在任何情况下都不会崩溃呢?我们可以在系统负载过高的时候采用限流、降级、熔断技术保护系统
Sentinel是阿里开源的一套用于服务容错的综合性解决方案,他以流量作为切入点,从流量口直熔断降级、系统负载保护等多个维度保护服务的稳定性。
直接:设定阈值类型和单机阈值,进行现在,当流量一旦超过设计的阈值则快速失败
关联:当关联的资源达到阈值的时候限流自己
链路:限流一条链路上的资源
RabbitMQ是一个消息代理:它接受和转发消息
异步解耦
系统间通过消息通信,不用关心其他系统的处理
流量消峰
比如一个购物系统,突然有前端产生的大量订单要存进数据库,这就产生的峰,这时可以使用Rabbitmq,把订单先存入消息队列里面,然后创建一个消费者慢慢的将订单存进数据库,这就实现了流量消峰
灵活路由
rabbit有6种工作模式,有多种灵活的方法转发队列中的消息
一.simple模式(即最简单的收发模式)
1.生产者产生消息,将消息放入队列
2.消息的消费者(consumer) 监听 消息队列,如果队列中有消息,就消费掉,消息被拿走后,自动从队列中删除(隐患 消息可能没有被消费者正确处理,已经从队列中消失了,造成消息的丢失,这里可以设置成手动的ack,但如果设置成手动ack,处理完后要及时发送ack消息给队列,否则会造成内存溢出)。
二.work工作模式(资源的竞争)
1.消息产生者将消息放入队列消费者可以有多个,消费者1,消费者2同时监听同一个队列,消息被消费。C1 C2共同争抢当前的消息队列内容,谁先拿到谁负责消费消息(隐患:高并发情况下,默认会产生某一个消息被多个消费者共同使用,可以设置一个开关(syncronize) 保证一条消息只能被一个消费者使用)。
三.publish/subscribe发布订阅(共享资源)
1、每个消费者监听自己的队列;
2、生产者将消息发给broker,由交换机将消息转发到绑定此交换机的每个队列,每个绑定交换机的队列都将接收到消息。
四.routing路由模式
1.消息生产者将消息发送给交换机按照路由判断,路由是字符串(info) 当前产生的消息携带路由字符(对象的方法),交换机根据路由的key,只能匹配上路由key对应的消息队列,对应的消费者才能消费消息;
2.根据业务功能定义路由字符串
3.从系统的代码逻辑中获取对应的功能字符串,将消息任务扔到对应的队列中。
4.业务场景:error 通知;EXCEPTION;错误通知的功能;传统意义的错误通知;客户通知;利用key路由,可以将程序中的错误封装成消息传入到消息队列中,开发者可以自定义消费者,实时接收错误;
五.topic 主题模式(路由模式的一种)
1.星号井号代表通配符
2.星号代表多个单词,井号代表一个单词
3.路由功能添加模糊匹配
4.消息产生者产生消息,把消息交给交换机
5.交换机根据key的规则模糊匹配到对应的队列,由队列的监听消费者接收消息消费
fanout:如果交换器收到消息,将会广播到所有绑定的队列上
direct:如果路由键完全匹配,消息就被投递到相应的队列
topic:可以使来自不同源头的消息能够到达同一个队列。 使用 topic 交换器时,可以使用通配符
设置手动Ack
消息回执
向服务器发送一个通知
告诉服务器一条消息已经处理完成
服务器可以通过Ack’,知道消费者时空闲还是繁忙
设置Qos=1
每次抓取消息的数量
消息处理完成之后不会抓取新的消息
手动Ack模式下才有效
redis是非关系型数据库,数据以键值对的形式存储进redis数据库。
redis可以存储键和五种类型值之间的映射
与传统的数据库不同Redis的数据是存在内存中的,所以读写非常快,每秒可处理超过10万次读写
String:常规key-value缓存应用。常规计数: 微博数, 粉丝数,统计文章字数。
hash:存储对象数据
list:关注列表,粉丝列表
set:集合要求值不能重复,可以应用在粉丝管理,粉丝不能有重复的
zset:与set类似,区别是set不是自动有序的
与macache一样,为了保证效率数据都是缓存道内存当中。
区别就是redis会周期性的把更新的数据写入磁盘或者把修改操作写入适当的记录文件,并且在此基础上实现主从同步。redis支持主从同步,数据库可以从主服务器向任意数量的从服务器上同步。从服务器可以关联其他服务器的主服务器器
redis-cli 进入控制台
exit 退出
shutdown 停止
keys * 查找全部
select 查找
del 删除
type 查看数据类型
flushall 清空所有数据库
flushdb 清空当前数据库
incr/incrby 递增
decr/decrby 递减
append 追加
strlen 字符串长度
mset 批量添加
mget 批量获取
Jedis和RedisTemplate
Jedis
1.编写测试类
2.构建Redis的客户端Jedis
Jedis jedis=new Jedis("192.168.138.129",6379)
Jedis("host",port)
3.在redis服务器端修改redis.conf文件
将 build 17.0.0.1 这个配置注释掉
将 protected-mode 改为 no
4.现在就可以使用Jedis操作对象了
jedis.ping()
pong
RedisTemplate
1.添加依赖
spring-boot-start-data-redis
2.yml配置
spring:
redis:
host: 192.168.138.129
port: 6379
3.使用RedisTempalte对象操作redis数据库
@Autowired
private StringRedisTemplate srt;
@Test
String pong=srt.getConnectionFactory().getConnection().ping();
System.out.pringtln(pong);
如果仅有string类型很难实现存储对象,更新对象属性这样的操作,hash可以存储这种有对象关系的数据
Redis的list类型其实就是一个每个子元素都是String类型的双向链表
可以通过push,pop操作从链表头部或者尾部添加删除元素
Redis的Set是String类型的无序集合。集合成员是唯一的,这意味着不能出现重复数据
Redis中Set集合是通过哈希表实现的,所以添加、删除、查找的复杂度都是1
redis在实际的使用过程中更多的用作缓存,然而缓存数据一般都需要设定有效时间,到期后数据自动销毁
如何设置?
expire key second 设置key值的有效时长
pexpire key milliseconds 设置时长以毫秒为单位(适用于秒杀)
如何查看?
TTL 查看key的剩余时间,当返回值为-2的时候,表示键被删除。当可以存在但是没有剩余时间的时候返回-1
Redis是一种内存数据库,在断电的时候数据可能会丢失
rdb 和 AOF 两种
rdb
Rdb方式是手动的,周期性的将内存中的数据做一个快照存储到磁盘中,快照文件叫做 .rdb
在redis中使用save/bgsave命令完成
Redis一般默认持久化的方法是rdb,系统启动时自动开启这种持久化方式
AOF
Aof是通过记录写操作日志方式,记录Redis数据的一种持久化方式,这个机制默认是没有开启的。
打开 Aof 持久化机制之后,redis每接受一次写命令就会写入日志文件中,当然先写入os cache隔一段时间酒fsync一下
redis重启时有限通过AOF进行数据恢复,因为使用AOF比较完整。
Redis save 命令执行一个同步保存操作,将当前Redis实例的所有数据快照以rdb 文件的形式保存到硬盘
bgsave命令执行后立即返回ok,然后redis会fork出一个新的子进程,原来的redis进程会继续处理客户端请求,而子进程负责将文件保存到磁盘然后退出
1.rdb会生成多个数据文件,每个文件都代表某一时刻redis中的数据,因此这种多数据文件方式非常适合做“冷备”,可以将这些数据发送到一些远程的安全存储上。
2.rdb对redis对外提供的读写服务影响小,可以让redis保持高性能。
为什么对读写影响小,保持高性能?
因为redis可以fork出一个子进程,让子进程执行对磁盘的IO
3.相对于Aof来讲,基于rdb数据文件重启和恢复redis进程更加快速
1.如果想要在redis故障时,如果想尽可能少丢失数据,那么rdb没有Aof
为什么没有Aof好?
因为rdb的save在每60s内数据超过1000才存入磁盘,若没有超过不会存入。若在某一个60s内数据没有超过1000,redis挂掉了,则这些数据也丢失了。
2.rdb每次在fork子进程来执行rdb快照数据文件生成,如果数据量过大时,可能会导致对客户提供的服务暂停数毫秒或者数秒
always
每次写入一条数据就立刻将这个数据写入日志然后fsync到磁盘
everysec
每隔1s写一次
no
由操作系统决定何时持久化
rewrite重写日志,会清除掉一些过期的日志
比如目前日志中存放了100万条数据的写日志,而redis中目前只有10w条数据,rewrite会根据这10w条数据重新构建一套新的日志到AOF覆盖之前的日志。
1.AOF更好的保护数据不丢失
2.AOF日志文件以append-only模式写入,所以没有任何的磁盘寻址的开销,效率非常的高,而且文件不容易破损,即使文件的尾部破损了也容易修复
3.AOF日志文件过大时,会进行后台重写操作进行压缩
4.AOF日志文件的命令通过非常可读的方式进行记录,这个特性非常适合做灾难性的误删操作的紧急修复。
对于同一份数据来说AOF日志文件比rdb体积更大
AOF脆弱
AOF支持写的QPS低
单个redis支持读写能力还是有限的,此时我们需要使用多个redis来提高redis的并发处理能力,这些redis如何协同工作,这就用到主从架构
构建Redis集群,使用主从架构。可以构建master和salver设计主从关系,master可以读也可以写、slave只可以读
哨兵(Sentinel)是Redis的主从架构模式下,实现高可用性(high availability)的一种机制。由一个或多个Sentinel实例(instance)组成的Sentinel系统(system)可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线(宕机了)状态时,自动将下线主服务器属下的某个从服务器(Slave)升级为新的主服务器(Master),然后由新的主服务器代替已下线的主服务器继续处理命令请求
1):每个Sentinel以每秒钟一次的频率向它所知的Master,Slave以及其他 Sentinel 实例发送一个 PING 命令(严格来讲就是心跳包)。
2):如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值(这个配置项指定了需要多少失效时间,一个master才会被这个sentinel主观地认为是不可用的。 单位是毫秒,默认为30秒), 则这个实例会被 Sentinel 标记为主观下线。
3):如果一个Master被标记为主观下线,则正在监视这个Master的所有 Sentinel 要以每秒一次的频率确认Master的确进入了主观下线状态。
4):当有足够数量的 Sentinel(大于等于配置文件指定的值)在指定的时间范围内确认Master的确进入了主观下线状态, 则Master会被标记为客观下线 。
5):在一般情况下, 每个 Sentinel 会以每 10 秒一次的频率向它已知的所有Master,Slave发送 INFO 命令 。
6):当Master被 Sentinel 标记为客观下线时,Sentinel 向下线的 Master 的所有 Slave 发送 INFO 命令的频率会从 10 秒一次改为每秒一次 。
7):若没有足够数量的 Sentinel 同意 Master 已经下线, Master 的客观下线状态就会被移除。
8): 若 Master 重新向 Sentinel 的 PING 命令返回有效回复, Master 的主观下线状态就会被移除。
博客数字统计
strlen
将审计日志追加到指定的key
append
博客点赞操作
incr
秒杀操作的计时是如何实现的
pexpire 以毫秒为单位设置时长
如何设置缓存时长
expire key second
pexpire key milliseconds
发布一篇博客 需要hmset
浏览博客 hmget
判断博客是否存在 hexist
分布式系统中你登陆成功以后是如何存储用户信息的
原因: 前端恶意提交无效的请求(穿透)、热点数据失效(击穿)
缓存中没有需要的数据,就会跳过缓存访问数据库,缓存中一直没有就一直访问数据库,此时缓存就跟摆设一样。这就是缓存穿透。
对于恶意的攻击请求,key值是空的,或者是其他数据库中不存在的key值。我们把这些无效数据加入缓存中,遇到恶意请求时直接返回null。这样阻止缓存穿透。
缺点:恶意的请求太多,自己枚举不完
在缓存之前设置一道过滤器,里面存有校验目前数据库中存有的key值的校验,请求发来时先进性过滤,符合规则才可以继续访问缓存,有效的防止缓存穿透。
存在Redis中的数据是有时间限制的,时间一到就会失效然后从缓存中清除出去。若不巧在某一时间大量的数据集体失效,大量的前端请求访问,但是缓存中的数据失效了,这些请求就会访问数据库这就是缓存的雪崩。
解决方案
设计缓存随机失效
设置sentinel或者Hystrix进行限流和熔断减少损失。
RABC
Role-Base Access Controler 基于角色的权限控制
Resource Base Access Controller 基于资源的权限控制
权限与角色相关联,用户通过成为适当的角色的成员而得到这些角色的权限,这极大的简化了权限管理。这样的管理层级依赖大,权限赋予角色,又把角色赋予给用户。
目的是设计一种通用的权限管理系统,不和任何实际的业务相关联,只做权限相关的管理和验证。
之所以资源为核心,是因为基于角色的限制。基于资源的权限系统是围绕资源树和组织树来进行权限的。
E:\Tencen\微信\WeChat Files\wxid_2nciglwhomi122\FileStorage\File\2021-07
数据保存在内存
优点: 存取速度快
缺点: 数据不能永久保存
数据保存在文件
优点: 数据永久保存
缺点:1)速度比内存操作慢,频繁的IO操作。2)查询数据不方便
数据保存在数据库
1)数据永久保存
2)使用SQL语句,查询方便效率高。
3)管理数据方便
索引是一种数据结构。数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。索引的实现通常使用B树及其变种B+树。
出数据之外,数据库系统还维护了为满足特定查找算法的数据结构,这种数据结构以某种特定的方式引用数据,这种数据结构就叫做数据库索引
更通俗的说,索引就相当于目录。为了方便查找书中的内容,通过对内容建立索引形成目录。索引是一个文件,它是要占据物理空间的。
当然是因为索引的优点
唯一索引、主键索引、符合索引、聚集索引、非聚集索引、聚簇索引、非聚簇索引、稠密索引、稀疏索引
alter table 表名 add index(字段名)
索引的数据结构和具体存储引擎的实现有关,在MySQL中使用较多的索引有Hash索引,B+树索引
默认是B+数索引
索引用来快速地寻找那些具有特定值的记录。如果没有索引,一般来说执行查询时遍历整张表。
索引的原理很简单,就是把无序的数据变成有序的查询
把创建了索引的列的内容进行排序
对排序结果生成倒排表
在倒排表内容上拼上数据地址链
在查询的时候,先拿到倒排表内容,再取出数据地址链,从而拿到具体数据
第一种方式:在执行CREATE TABLE时创建索引
第二种方式:使用ALTER TABLE命令去增加索引
ALTER TABLE table_name ADD INDEX index_name (column_list);
第三种方式:使用CREATE INDEX命令创建
CREATE INDEX index_name ON table_name (column_list);
关于索引:由于索引需要额外的维护成本,因为索引文件是单独存在的文件,所以当我们对数据的增加,修改,删除,都会产生额外的对索引文件的操作,这些操作需要消耗额外的IO,会降低增/改/删的执行效率。所以,在我们删除数据库百万级别数据的时候,查询MySQL官方手册得知删除数据的速度和创建的索引数量是成正比的。
所以我们想要删除百万数据的时候可以先删除索引(此时大概耗时三分多钟)
然后删除其中无用数据(此过程需要不到两分钟)
删除完成后重新创建索引(此时数据较少了)创建索引也非常快,约十分钟左右。
总结:先删除索引再删除数据
使用B树的好处
B树可以在内部节点同时存储键和值,因此,把频繁访问的数据放在靠近根节点的地方将会大大提高热点数据的查询效率。这种特性使得B树在特定数据重复多次查询的场景中更加高效。
使用B+树的好处
由于B+树的内部节点只存放键,不存放值,因此,一次读取,可以在内存页中获取更多的键,有利于更快地缩小查找范围。 B+树的叶节点由一条链相连,因此,当需要进行一次全数据遍历的时候,B+树只需要使用O(logN)时间找到最小的一个节点,然后通过链进行O(N)的顺序遍历即可。而B树则需要对树的每一层进行遍历,这会需要更多的内存置换次数,因此也就需要花费更多的时间
事务是一个不可分割的数据库操作序列,也是数据库并发控制的基本单位,其执行的结果必须使数据库从一种一致性状态变到另一种一致性状态。事务是逻辑上的一组操作,要么都执行,要么都不执行。
原子性: 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
一致性: 执行事务前后,数据保持一致,多个事务对同一个数据读取的结果是相同的;
隔离性: 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
持久性: 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
脏读(Drity Read):某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个RollBack了操作,则后一个事务所读取的数据就会是不正确的。
不可重复读(Non-repeatable read):在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新的原有的数据。
幻读(Phantom Read):在一个事务的两次查询中数据笔数不一致,例如有一个事务查询了几列(Row)数据,而另一个事务却在此时插入了新的几列数据,先前的事务在接下来的 查询中,就会发现有几列数据是它先前所没有的。
char的特点
char表示定长字符串,长度是固定的;
如果插入数据的长度小于char的固定长度时,则用空格填充;
因为长度固定,所以存取速度要比varchar快很多,甚至能快50%,但正因为其长度固定,所以会占据多余的空间,是空间换时间的做法;
对于char来说,最多能存放的字符个数为255,和编码无关
varchar的特点
varchar表示可变长字符串,长度是可变的;
插入的数据是多长,就按照多长来存储;
varchar在存取方面与char相反,它存取慢,因为长度不固定,但正因如此,不占据多余的空间,是时间换空间的做法;
对于varchar来说,最多能存放的字符个数为65532
优化shema、sql语句+索引;
第二加缓存,memcached, redis;
主从复制,读写分离;
垂直拆分,根据你模块的耦合度,将一个大的系统分为多个小的系统,也就是分布式系统;
水平切分,针对数据量大的表,这一步最麻烦,最能考验技术水平,要选择一个合理的sharding key, 为了有好的查询效率,表结构也要改动,做一定的冗余,应用也要改,sql中尽量带sharding key,将数据定位到限定的表上去查,而不是扫描全部的表;
系统的吞吐量瓶颈往往出现在数据库的访问速度上
优化成本:硬件> 系统配置> 数据库表结构 >SQL索引
优化效果:硬件< 系统配置<数据库表结构 1.尽量避免在字段开头模糊查询,会导致数据库引擎放弃索引扫描全表 例如:select * from t where username like ‘%陈%’; 优化方式:尽量在字段后面进行模糊查询 select * from t where username like ‘陈%’; 2.尽量避免使用 in/not in 会导致引擎扫描全表 例如: select * from t where id in(2,3); 优化方式:如果是连续的数值使用between代替 select * from t where id between 2 and 3 3.尽量避免使用or 例如: select * from t where id=2 or id=3; 优化方式:使用union拼接查询结果 select * from t where id=2 union select * from t where id=3; 4.尽量避免null值的判断 select * from t where s is null; 优化方式:可以给字段添加默认值0,对0值进行判断 select * from t where s=0 5.尽量避免在where条件中等号左侧进行表达式函数 例如: select * from t where s/10=9; 优化 select * from t where s=9*10; 6.使用索引做条件查询的时候,尽量避免使用 < > != 等判断条件 7.Order By条件要与where中条件一致,否则order by不会利用索引排序 例如:select * from t order by age; 优化 select * from t where age>0 order by age; where可以在没有筛选出字段的时候也可以使用,where是从数据表中的字段直接进行的筛选的。 比如: select后面 当select的内容中有聚合函数的话只能用having。 select MIN(Amount) from Details; max求最大值 sum求总和 count求数据的条数 8个钩子函数 beforeCreate created beforeMont monted beforeUpdate updated beforeDistory distoryed 前端ajax的回调函数层级嵌套,上一层回调函数的结果是下一层回调函数的参数,这样的层层嵌套,如果有一层回调结果出错就会导致结果出错,这就是回调地狱问题。 如何解决回调地狱: 主要使用的是阿里的 Seata框架 Seata At事务分两个阶段来管理全局事务 第一阶段:执行各分支事务 第二阶段:控制全局事务最终提交或者回滚 微服务系统中,各服务之间无法相互感知事务是否执行成功,这时就需要一个专门的服务,来协调各个服务的运行状态。这个服务称为 TC(Transaction Coordinator),事务协调器。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LArij8fE-1628644245466)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805145313386.png)] 订单系统开始执行保存订单之前,首先启动 TM(Transaction Manager,事务管理器),由 TM 向 TC 申请开启一个全局事务:这时TC会产生一个全局事务ID,称为 XID,并将 XID 传回 TM [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rmBc2pWX-1628644245468)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805145421133.png)] [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6K34uaJR-1628644245470)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805145831160.png)] 这样就开启了全局事务! 全局事务开启后,开始执行创建订单的业务。首先执行保存订单,这时会先启动一个 RM(Resource Manager,资源管理器),并将 XID 传递给 RM。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aXex0YeV-1628644245473)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805145920531.png)] RM 负责对分支事务(即微服务的本地事务)进行管理,并与 TC 通信,上报分支事务的执行状态、接收全局事务的提交或回滚指令。 RM 首先会使用 XID 向 TC 注册分支事务,将分支事务纳入对应的全局事务管辖 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xnpBeOs7-1628644245474)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805150007631.png)] 现在可以执行保存订单的分支事务了。一旦分支事务执行成功,RM 会上报事务状态: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Pg5qpvxZ-1628644245476)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805150131603.png)] TC 收到后,会将该状态信息传递到 TM [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kK9Vvbzt-1628644245477)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805150156454.png)] 到此,保存订单过程结束。下面是调用库存服务,减少商品库存,与订单的执行过程相同。 首先调用库存服务,启动 RM,并传递 XID: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gOtLQtwv-1628644245479)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805150531072.png)] 库存服务的 RM 使用 XID 向 TC 进行注册,纳入全局事务管辖: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mMpOEIw5-1628644245480)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805150619614.png)] 执行本地事务成功后上报状态,TC会将状态发送给TM: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sm6XhqY6-1628644245482)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805150655838.png)] 完成账户分支事务 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BDyXc2Su-1628644245483)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805150852828.png)] TM(全局事务管理器)收集齐了全部分支事务的成功状态,它会进行决策,确定全局事务成功,向 TC 发送全局事务的提交请求: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XVtdpNB7-1628644245484)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805151001405.png)] TC 会向所有 RM 发送提交操作指令,RM 会完成最终提交操作: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pdEO5NGA-1628644245487)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805151259507.png)] 全局事务全部提交完成! 假设订单业务执行过程中,扣减账户金额这一步分支事务执行失败,那么失败状态对TC上报,然后再发送给TM [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zv2BKCuy-1628644245488)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805151442868.png)] TM 会进行决策,确定全局事务失败,向 TC 发送全局事务的回滚请求: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VhXCaYmV-1628644245489)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805151507982.png)] 然后,TC 会向所有 RM 发送回滚操作指令,RM 会完成最终回滚操作 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kCb5l0N6-1628644245490)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805160157049.png)] [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B8lUDcUe-1628644245491)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805161348485.png)] 第一阶段 Try [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aWJXMKD3-1628644245493)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805163558966.png)] 假如用户购买 100 元商品,要扣减 100 元。 TCC 事务首先对这100元的扣减金额进行预留,或者说是先冻结这100元: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CKpYI1Qf-1628644245494)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805163608157.png)] 第二阶段 Confirm 当全局事务提交时,就可以使用冻结的金额来最终实现业务数据操作: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IeJEib0W-1628644245495)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805163623571.png)] 第二阶段 Cancel [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nEG3ntMx-1628644245497)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805164134069.png)] 多个事务并发的情况 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r56mlqHI-1628644245498)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805164153738.png)] 常见的运行时异常有哪些? 在代码里面如何处理这些异常呢? 部署项目出错了,怎么在Linux里面查看错误的日志? 十万条数据如何快速插入到数据库里面?如何快速删除 创建目录 删除 强制删除 修改文件内容SQL优化的具体操作
20.Where和having的区别
1)select addtime,name from dw_users where addtime> 1500000000
2)select addtime,name from dw_users having addtime> 1500000000
多表联查
一、内连接 返回两个表的公共记录
select * from 表1 inner join 表2 on 表1.公共字段=表2.公共字段
二、左外连接 以左边的表为准,右边如果没有对应的记录用null显示
select * from 表1 left join 表2 on 表1.公共字段=表2.公共字段
三、右外连接 以右边的表为准,左边如果没有对应的记录用null显示
select * from 表1 right join 表2 on 表1.公共字段=表2.公共字段
聚合函数
select AVG(Amount) from Details;
select MAX(Amount) from Details;
select SUM(Amount) from Details;
select COUNT(Amount) from Details;前端
1.VUE声明周期
2.什么是地狱回调问题
分布式事务
Seata AT
第一阶段:执行各分支事务
第二阶段:控制全局事务最终提交
如果失败第二阶段:控制全局事务最终回滚
TCC与AT 事务的主要区别为:
每个阶段的数据操作都要自己进行编码来实现,事务框架无法自动处理。
不必对数据加全局锁,允许多个事务同时操作数据。Seata TCC
以账户服务为例,当下订单时要扣减用户账户金额:
如果第一阶段能够顺利完成,那么说明“扣减金额”业务(分支事务)最终肯定是可以成功的。当全局事务提交时, TC会控制当前分支事务进行提交,如果提交失败,TC 会反复尝试,直到提交成功为止。
如果全局事务回滚,就把冻结的金额进行解冻,恢复到以前的状态,TC 会控制当前分支事务回滚,如果回滚失败,TC 会反复尝试,直到回滚完成为止。
多个TCC全局事务允许并发,它们执行扣减金额时,只需要冻结各自的金额即可:面试难题
Linux
Linux常见的一些命令
一、目录操作
pwd 查看当前工作目录
clear 清除屏幕
cd ~ 当前用户目录
cd / 根目录
cd - 上一次访问的目录
cd .. 上一级目录
mkdir aaa 在当前目录下创建aaa目录,相对路径;
mkdir ./bbb 在当前目录下创建bbb目录,相对路径;
mkdir /ccc 在根目录下创建ccc目录,绝对路径;
二、文件操作
rm -r a.java 删除当前目录下的a.java文件(每次回询问是否删除y:同意)
1
rm -rf a.java 强制删除当前目录下的a.java文件
三、文件内容操作(查看日志,更改配置文件)
vim a.java 进入一般模式
i(按键) 进入插入模式(编辑模式)
ESC(按键) 退出
:wq 保存退出(shift+:调起输入框)
:q! 不保存退出(shift+:调起输入框)(内容更改)
:q 不保存退出(shift+:调起输入框)(没有内容更改)
四、压缩和解压缩
tar -zcvf start.tar.gz a.java b.java 将当前目录下a.java、b.java打包
tar -zcvf start.tar.gz ./* 将当前目录下的所欲文件打包压缩成haha.tar.gz文件
tar -xvf start.tar.gz 解压start.tar.gz压缩包,到当前文件夹下;
tar -xvf start.tar.gz -C usr/local(C为大写,中间无空格)
解压start.tar.gz压缩包,到/usr/local目录下;
Linux下怎么部署自己的项目?部署项目的流程