java 面试笔记

JAVA面试知识点

String、StringBuffer与StringBuilder之间区别

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

数组和链表的区别

1.数组可以随机访问性,查找速度快。链表不能随机查找,必须从第一个开始遍历,查找效率低。
2.数组大小固定,要求有足够的连续内存空间,可能浪费内存。链表大小没有固定,拓展很灵活,内存利用率高,不会浪费内存
3.数组插入和删除效率低,链表插入删除速度快。

Arraylist和linkedlist区别

1.ArrayList基于动态数组实现,LinkedList基于双向链表实现。
2.ArrayList主要控件开销在于需要在List列表预留一定空间;而LinkList主要控件开销在于需要存储节点信息以及节点指针。
3.当对数据进行增加和删除的操作(add和remove操作)时,LinkedList比ArrayList的效率更高(arraylist需要去调用System.arraycopy函数操作内存中的数据搬移,相较效率较低,数组浅拷贝),当随机访问List(get和set操作)时,ArrayList比LinkedList的效率更高。

arraylist扩容

初始容量10,扩容时扩大为原来的1.5倍,调用Arrays.copyOf复制原来数组中的元素到新的扩容后的数组中。
1 扩容容量不能太⼩,防⽌频繁扩容,频繁申请内存空间 + 数组频繁复制。
2 扩容容量不能太⼤,需要充分利⽤空间,避免浪费过多空间;
缩容
ArrayList的缩容,需要我们自己手动去调用trimToSize()方法
在这里插入图片描述

jvm内存结构

在这里插入图片描述
JVM中的堆,一般分为三大部分:新生代、老年代、永久代
 jdk1.8后移除永久代。元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。只存储类和类加载器的元数据信息。
虚拟机栈内存用来存储存储局部变量表、操作数栈(根据字节码指令,往栈中写入数据或提取数据)、动态链接(将符号引用转换为调用方法的直接引用,指向运行时常量池的方法引用)、方法返回地址(存放调用该方法的pc寄存器的值)。
堆内存用来存储Java中的对象。
方法区是被所有线程共享的内存区域,用来存储已被虚拟机加载的类信息、常量、静态变量等。
本地方法栈为虚拟机使用到的Native方法服务。
程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。

作用

JVM是java字节码执行的引擎,还能优化java字节码,使之转化成效率更高的机器指令。
不同的平台对应着不同的JVM,在执行字节码(class文件)时,JVM负责将每一条要执行的字节码送给解释器,解释器再将其翻译成特定平台环境的机器指令并执行,这样就实现了跨平台运行。
垃圾回收处理。

堆和栈区别

1.堆内存用来存放由new创建的对象和数组。
2.栈内存用来存放方法或者局部变量等
3.堆是先进先出,后进后出
4.栈是后进先出,先进后出

查看jvm状态

jmap 观察运行中的jvm物理内存的占用情况。

新生代(Minor GC)

主要是用来存放新生的对象。一般占据堆的1/3空间。由于频繁创建对象,所以新生代会频繁触发MinorGC进行垃圾回收。

新生代又分为 Eden区、ServivorFrom、ServivorTo三个区。

Eden区:Java新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老年代)。当Eden区内存不够的时候就会触发MinorGC,对新生代区进行一次垃圾回收。
ServivorTo:保留了一次MinorGC过程中的幸存者。
ServivorFrom:上一次GC的幸存者,作为这一次GC的被扫描者。
当JVM无法为新建对象分配内存空间的时候(Eden满了),Minor GC被触发。因此新生代空间占用率越高,Minor GC越频繁。

MinorGC的过程:采用复制算法。

首先,把Eden和ServivorFrom区域中存活的对象复制到ServicorTo区域(如果有对象的年龄以及达到了老年的标准,一般是15,则赋值到老年代区)
同时把这些对象的年龄+1(如果ServicorTo不够位置了就放到老年区)
然后,清空Eden和ServicorFrom中的对象;最后,ServicorTo和ServicorFrom互换,原ServicorTo成为下一次GC时的ServicorFrom区。

老年代(full gc)

老年代的对象比较稳定,所以MajorGC不会频繁执行。

在进行MajorGC前一般都先进行了一次MinorGC,使得有新生代的对象晋身入老年代,导致空间不够用时才触发。当无法找到足够大的连续空间分配给新创建的较大对象时也会提前触发一次MajorGC进行垃圾回收腾出空间。

MajorGC采用标记—清除算法:

首先扫描一次所有老年代,标记出存活的对象
然后回收没有标记的对象。
MajorGC的耗时比较长,因为要扫描再回收。MajorGC会产生内存碎片,为了减少内存损耗,我们一般需要进行合并或者标记出来方便下次直接分配。

当老年代也满了装不下的时候,就会抛出OOM(Out of Memory)异常。

什么对象直接放入老年代

我们知道,老年代放不下后就会触发Full GC,导致stop the world,那么我们的目标就是尽量不要触Full GC,要做到这个就希望除了线城池,spring bean等这种要一直存存活的对象,其它对象的生命周期再新生代就结束,不要存活到老年代,这样子就可以尽量保证老年代内存充足,很少触发full gc,那么再此之前,我们得知道对象在什么时候会移动到老年代中。
1、大对象直接进入老年代
大对象就是需要大量连续内存空间的对象(比如:字符串、数组),JVM参数-XX:PretenureSizeThreshold可以设置大对象的大小,如果对象超过设置大小会直接进入老年代,不会进入年轻代,这个参数只在Serial和ParNew两个收集器下有效。
比如设置JVM参数:-XX:PretenureSizeThreshold=1000000 -XX:+UseSerialGC

那么超过1000000字节也就是差不多1M的对象会直接进入老年代。

为什么要这样呢?
我们知道新生代一般都用复制算法,为了避免为大对象分配内存时的复制操作而降低效率。

2、长期存活的对象将进入老年代
虚拟机才有分代收集的思想来管理内存,给每一个对象一个对象年龄(Age)计数器。如果对象在Eden出生并经过第一次Minor GC后仍能够存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并将对象年龄设为1.对象在Survivor中每熬过一次MinorGC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数-XX:MaxTenuringThreshold来设置。

3、对象动态年龄判断
Survivor区有两块区域from和to区,其中任何一块区域,一批对象的总大小大于这块Survivor区域内存大小的50%,那么此时大于等于这批对象年龄最大值的对象,就可以直接进入老年代了,例如Survivor区域里现有一批对象:年龄1对象+年龄2对象+…+年龄n对象的多个年龄对象总和超过了Survivor放这一批对象的那一块区域的50%,此时就会把年龄n(包括年龄n)以上的对象都放入老年代。

为什么要这样呢?
其实是希望那些可能是长期存活的对象,尽早进入老年代,对象动态年龄判断机制一般是在minor gc之后触发的。

4、Minor gc后存活的对象Survivor区放不下
这种情况会把存活的对象部分挪到老年代,部分可能还会放在Survivor区。

Minor GC触发机制和Full GC触发机制

Minor GC触发机制:
当年轻代满时就会触发Minor GC,这里的年轻代满指的是Eden代满。
Full GC触发机制:
通过Minor GC后进入老年代的对象大小大于老年代的可用内存 。
详解博客:https://www.cnblogs.com/cuijj/p/10499621.html

回收条件

判断一个类是否“无用”,则需同时满足三个条件:

(1)、该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例;

(2)、加载该类的ClassLoader已经被回收

(3)、该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

回收对象

堆是Java虚拟机进行垃圾回收的主要场所,其次要场所是方法区。

回收算法

标记清除算法:“标记-清除”算法,如它的名字一样,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。
复制算法:一开始就会将可用内存分为两块,from域和to域, 每次只是使用from域,to域则空闲着。当执行GC操作,这个时候,会把from域存活的对象拷贝到to域,然后直接把from域进行内存清理。 最后将frmo域和to域互换,from域作为下次GC时的to域。
标记整理算法:首先标记出所有需要回收的对象,让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

停顿现象

GC任务是识别和回收垃圾对象,进行内存清理
为了让GC可以高效的执行,在进行GC时,系统会进入一个停顿的状态
停顿目的
终止所有应用线程
只有这样,系统才不会有新的垃圾产生

不产生停顿 Epsilon GC

性能测试(它可以帮助过滤掉GC引起的性能假象)
内存压力测试(例如,知道测试用例 应该分配不超过1GB的内存, 我们可以使用-Xmx1g –XX:+UseEpsilonGC, 如果程序有问题, 则程序会崩溃)

垃圾回收器

  • 串行处理器:
    –适用情况:数据量比较小(100M左右);单处理器下并且对响应时间无要求的应用。
    –缺点:只能用于小型应用
  • 并行处理器:
    –适用情况:“对吞吐量有高要求”,多CPU、对应用响应时间无要求的中、大型应用。举例:后台处理、科学计算。
    –缺点:应用响应时间可能较长
  • 并发处理器(CMS):
    –适用情况:“对响应时间有高要求”,多CPU、对应用响应时间有较高要求的中、大型应用。举例:Web服务器/应用服务器、电信交换、集成开发环境。

jdk1.7 默认垃圾收集器Parallel Scavenge(并行垃圾收集器)(新生代)+Parallel Old(老年代)

jdk1.8 默认垃圾收集器Parallel Scavenge(并行垃圾收集器)(新生代)+Parallel Old(老年代)

jdk1.9 默认垃圾收集器G1
并行(parallel):指在同一时刻,有多条指令在多个处理器上同时执行。
并发(concurrency):指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。

CMS和G1 2个垃圾收集器区别

1.CMS垃圾回收器是老年代的垃圾回收器,可以配合新生代的Serial和ParNew垃圾回收器一起使用。G1收集器收集范围是老年代和新生代。不需要结合其他收集器使用
2.CMS收集器是使用“标记-清除”算法进行的垃圾回收,容易产生内存碎片
G1收集器使用的是“标记-整理”算法,进行了空间整合,降低了内存空间碎片。
3.CMS收集器以最小的停顿时间为目标的收集器。
G1收集器可控制垃圾回收的停顿时间

java基础类型(8)和大小

int 32bit 4字节
short 16bit 2字节
long 64bit 8字节
byte 8bit 1字节
char 16bit 2字节
float 32bit 4字节
double 64bit 8字节
boolean 1bit 1字节
bit 位:位是计算机中存储数据的最小单位,指二进制数中的一个位数,其值为“0”或“1”。
byte 字节:字节是计算机存储容量的基本单位,一个字节由8位二进制数组成。在计算机内部,一个字节可以表示一个数据,也可以表示一个英文字母,两个字节可以表示一个汉字。
包装类占用内存
包装之后,对于对象占用内存的计算方法和普通对象的大小计算完全一样,总共内存占用包含3各部分:(1)一个对象有对象头(32位机对象头占用8字节,64位机占用16字节)。(2)对象数据占用大小(Integer对象内部就是int,占用4字节)。(3)padding(需要8字节对齐,总占用没达到8字节倍数的要做填充,如Integer需要填充4字节,Integer在32位机中总共占用16字节,在64位机中总共占用24字节)。

Integer比较

如果两个Integer直接赋值比较,若值在-128到127之间,则会通过常量池来比较。否则比较内存地址。两值直接用==进行比较时,实际上比较的是两个不同对象的地址,而不是比较的两个对象的值大小。如果要比较两者的大小要用Integer重写的Object的equals()方法。

TCP/IP

3次握手是为了防止重复连接和确保客户端能接收服务端的传输。
在这里插入图片描述

在这里插入图片描述

第一次挥手:客户端发送一个FIN=M,用来关闭客户端到服务器端的数据传送,客户端进入FIN_WAIT_1状态。意思是说"我客户端没有数据要发给你了",但是如果你服务器端还有数据没有发送完成,则不必急着关闭连接,可以继续发送数据。
第二次挥手:服务器端收到FIN后,先发送ack=M+1,告诉客户端,你的请求我收到了,但是我还没准备好,请继续你等我的消息。这个时候客户端就进入FIN_WAIT_2 状态,继续等待服务器端的FIN报文。
第三次挥手:当服务器端确定数据已发送完成,则向客户端发送FIN=N报文,告诉客户端,好了,我这边数据发完了,准备好关闭连接了。服务器端进入LAST_ACK状态。
第四次挥手:客户端收到FIN=N报文后,就知道可以关闭连接了,但是他还是不相信网络,怕服务器端不知道要关闭,所以发送ack=N+1后进入TIME_WAIT状态,如果Server端没有收到ACK则可以重传。服务器端收到ACK后,就知道可以断开连接了。客户端等待了2MSL(最大分段生存期默认2分钟,任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。)后依然没有收到回复,则证明服务器端已正常关闭,那好,我客户端也可以关闭连接了。最终完成了四次握手。等待2MSL时间主要目的是怕最后一个ACK包对方没收到,那么对方在超时后将重发第三次握手的FIN包,主动关闭端接到重发的FIN包后可以再发一个ACK应答包。
为什么需要四次挥手?
因为TCP是一个全双工协议,必须单独拆除每一条信道。4次挥手的目的是终止数据传输,并回收资源,此时两个端点两个方向的序列号已经没有了任何关系,必须等待两方向都没有数据传输时才能拆除虚链路,不像初始化时那么简单,发现SYN标志就初始化一个序列号并确认SYN的序列号。因此必须单独分别在一个方向上终止该方向的数据传输。

如果是三次挥手,会怎么样?三次的话,被动关闭端在收到FIN消息之后,需要同时回复ACK和Server端的FIN消息。如果Server端在该连接上面并没有Pending的消息要处理,那么是可以的,如果Server端还需要等待一段时间才可以关闭另外一个方向的连接,那么这样的三次挥手就不能满足条件。

大量time_wait状态出现原因和解决

原因
大量的短连接存在;
特别是 HTTP 请求中,如果 connection 头部取值被设置为 close 时,基本都由服务端发起主动关闭连接;
tcp 四次挥手关闭连接机制中,为了保证 ACK 重发和丢弃延迟数据,设置 time_wait 为 2 倍的 MSL(报文最大存活时间);
time-wait 状态
tcp 连接中,主动关闭连接的一方出现的状态;(收到 FIN 命令,进入 time-wait 状态,并返回 ACK 命令)
保持 2 个 MSL 时间,即 4 分钟;(MSL 为 2 分钟)
解决
客户端,HTTP 请求的头部,connection 设置为 keep-alive,保持存活一段时间,现在的浏览器,一般都这么进行了;
服务器端,设置允许 time_wait 状态的 socket 被重用;
服务器端,缩减 time_wait 时间,设置为 1 MSL;(即 2 分钟)

OOM原因和解决方案

1.堆溢出

原因
代码中可能存在大对象分配
可能存在内存泄露,导致在多次GC之后,还是无法找到一块足够大的内存容纳当前对象。
内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
解决方案
加大堆内存、检查是否存在大对象的分配,如大数组分配。
某个对象的内存占用无限增长,发生了内存泄漏。比如某个service中使用了ThreadLocal但是事后没有remove,导致它维护的ThreadLocalMap越来越大。最终溢出。

2.方法区溢出

原因
存放了被虚拟机加载的类信息、常量、静态变量、JIT编译后的代码
运行期间生成了大量的代理类
解决方法
检查代码中是否存在大量的反射操作
检查是否方法区内存设置的过小

3.栈溢出

原因
出现这种异常,基本上都是创建的了大量的线程导致的
解决方法
限制线程总数或降低线程栈大小的容量

CAS原子性和ABA问题

现代的大多数CPU都实现了CAS,它是一种无锁(lock-free),且非阻塞的一种算法,保持数据的一致性
在这里插入图片描述
AtomicInteger是 volatile 不具备原子性的解决方案之一
ABA 解决 增加个版本号
AtomicStampedReference 和 AtomicMarkableReference 就能避免ABA问题,这个类维护了一个“版本号”Stamp“,在进行CAS操作的时候,不仅要比较当前值,还要比较版本号。只有两者都相等,才执行更新操作。

actomic类

基本数据类型原子类
相关实现类:AtomicBoolean、AtomicInteger、AtomicLong。主要就是用来基本类型的操作
数组类型原子类
相关实现类:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray。可以原子化地更新数组里面的每一个元素,和原子更新基本类型差别就是基本上方法都多了个数组的下标。
引用类型原子类
相关实现类:AtomicReference、AtomicStampedReference 、AtomicMarkableReference。这个就要重点关注ABA,但是Java已经关注到了所以AtomicStampedReference 和 AtomicMarkableReference 就能避免ABA问题了,就是用了版本号!
字段类型原子类。
相关实现类:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater

它们可以原子化地更新对象的属性,注意更新类的字段(属性)必须使用public volatile修饰符,这样才能保证可见性。

Redis

Redis为什么快

    1. Redis是纯内存数据库,相对于读写磁盘,读取速度快很多
    2. Redis采用了单线程的模型,使用的是非阻塞IO,IO多路复用,单线程来进行轮询,减少了线程切换时上下文的切换和竞争。
    3.Redis全程使用hash结构,读取速度快,还有一些特殊的数据结构,对数据存储进行了优化,如压缩表,对短数据进行压缩存储,再如,跳表,使用有序的数据结构加快读取的速度。

多路复用IO
“多路”指的是多个网络连接,“复用”指的是复用同一个线程。采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络IO的时间消耗)。可以直接理解为:单线程的原子操作,避免上下文切换的时间和性能消耗;加上对内存中数据的处理速度,很自然的提高redis的吞吐量。

sds

Redis 只会使用 C 字符串作为字面量,在大多数情况下,Redis 使用 SDS (Simple Dynamic String,简单动态字符串)作为字符串表示。
在这里插入图片描述
常数级别获取字符串长度
杜绝缓冲区溢出(C字符串不记录自身长度带来的问题是容易造成 缓冲区溢出。 SDS 的空间分配策略则完全杜绝了发生缓冲区溢出的可能性: 当 SDS API 需要对 SDS 进行修改时,API 会先检查 SDS 的空间是否满足修改所需的要求,如果不满足的话 API 会自动将 SDS 的空间进行扩容,然后再执行修改操作,所以 相比于 C 字符串,SDS完全杜绝了缓冲区溢出的问题 。)
减少字符串长度修改所需的内存再分配次数(空间预分配(额外分配空间)和惰性空间释放)。
二进制安全(C 字符串中的字符必须符合某种编码(比如 ASCII),并且除了字符串的结尾外,字符串里面不能包含空字符(‘/0’),sdsAPI 都会以处理二进制的方式来处理 SDS 存放的 buf 数组里的数据,程序不会对其中的数据做任何限制、过滤或者假设, 数据在写入时是什么样的,它被读取时就是什么样的 。)。

事务

开启关闭事务multi和exec
取消事务discard
监控watch和unwatch(CAS乐观锁)
WATCH命令可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令(事务中的命令是在EXEC之后才执行的,EXEC命令执行完之后被监控的键会自动被UNWATCH)。UNWATCH的作用是取消WATCH命令对多有key的监控,所有监控锁将会被取消。watch用于事务开始前。

Redis数据类型

在这里插入图片描述

Redis用处

Set实现共同关注、共同好友等交集、并集功能
Zset类型通常用来实现排行榜等功能
List微博关注列表按照好友的关注顺序显示,微信消息的显示列表按照消息到达的先后顺序进行显示
String单值缓存:使用 set、get 指令完成对数据的缓存
对象缓存:value部分使用JSON格式数据进行存储
计数器:使用 incr、decr 指令完成对数据的计数,比如朋友圈点赞数
Hash类型 对象缓存
Hyperloglog:访问人数
bitmaps:打卡
geospatial :用户距离
在这里插入图片描述

redis集群

主从复制
将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点
(master/leader),后者称为从节点(slave/follower);数据的复制是单向的,只能由主节点到从节点。
Master以写为主,Slave 用来读数据。
默认情况下,每台Redis服务器都是主节点;
主机可以写,从机不能写只能读!主机中的所有信息和数据,都会自动被从机保存。
复制原理
Slave 启动成功连接到 master 后会发送一个sync同步命令
Master 接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行
完毕之后,master将传送整个数据文件到slave,并完成一次完全同步。
全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
增量复制:Master 继续将新的所有收集到的修改命令依次传给slave,完成同步
但是只要是重新连接master,一次完全同步(全量复制)将被自动执行! 我们的数据一定可以在从机中看到!
哨兵模式
自动选举主节点的模式
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。

redis 持久化

RDB(默认使用):Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。(dump.rdb)
默认:save 3600 1
save 300 100
save 60 10000
触发机制:
1、save的规则满足的情况下,会自动触发rdb规则
2、执行 flushall 命令,也会触发我们的rdb规则!
3、退出redis,也会产生 rdb 文件!
需要一定的时间间隔进程操作!如果redis意外宕机了,这个最后一次修改数据就没有的了。
AOF:以日志的形式来记录每个写操作,将Redis执行过的所有指令记录下来(读操作不记录),只许追加文件.但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作.
相对于数据文件来说,aof远远大于 rdb,修复的速度也比 rdb慢,但aof数据完整性高。
1、在存储数据后,记录日志的服务器宕机了。这样就会导致这次存储的数据丢失; 2、记录日志和存储数据都是采用的主线程,那当记录日志时,就不可避免的会导致主线程的阻塞,影响Redis的性能。

java双亲委派

当某个类加载器需要加载某个.class文件时,它首先把检查自己是否加载过,如果有那就无需再加载了。如果没有,这个任务委托给他的父类加载器,父类中同理也会先检查自己是否已经加载过,如果没有再往上。直到BootstrapClassLoader,已经没有父加载器了,这时候开始考虑自己是否能加载了,如果自己无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出ClassNotFoundException。
在这里插入图片描述
双亲委派机制的作用
1、防止重复加载同一个.class,保证数据安全。
2、保证核心.class不能被篡改。
3.不同的加载器加载同一个.class也不是同一个Class对象。这样保证了Class执行安全(被篡改也不会被加载,即使被加载也不会是同一个class对象)。
tomocat打破双亲委派机制
如果使用默认的类加载器机制,那么是无法加载两个相同类库的不同版本的,默认的类加载器只会加载一份。这是双亲委派模型的隔离性。

synchronized和lock的实现和区别

在这里插入图片描述
synchronized是关键字,lock是接口
lock可以响应中断,如果当前持有锁则释放锁,sync不可
lock可以尝试超时获取锁,sync不可
lock可以获知是否有线程在等待当前的锁,sync不可
synchronized无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁,且锁升级的顺序是不可逆的。
当 JVM 进入安全点(SafePoint)的时候,会检查是否有闲置的 Monitor,然后试图进行降级。
公平锁:多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁。

synchronized作用域

synchronized修饰代码块:只作用于同一个对象,如果不同对象调用该代码块就不会同步。
synchronized修饰方法:和同步代码块一样,只作用于同一个对象。
synchronized修饰静态方法:作用于整个类。
synchronized修饰类:作用于整个类,两个线程调用同一个类的不同对象上的这种同步语句,也会进行同步。

TCP和UDP区别

在这里插入图片描述

在这里插入图片描述
1、TCP面向连接(如打电话要先拨号建立连接),发送数据前需要建立连接;UDP是无连接的,发送数据之前不需要建立连接

2、TCP提供可靠的服务。通过TCP连接可以无差错且按序传送的数据,;UDP尽最大努力交付,不保证可靠交付

3、TCP面向字节流,将数据以字节流的形式传递给接收者;UDP是面向报文的。

4、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信

5、TCP首部开销20字节;UDP的首部开销小,只有8个字节

6、TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道

TCP 1对1 UDP支持一对一,一对多,多对一和多对多的交互通信

扩展
TCP面向字节流与UDP面向报文
之前对于tcp和udp只是记住了一个面向字节流,一个是面向报文的,但是并没有真正的理解,经过中间找工作的压力中间不停的面试和笔试,现在终于对于这两个概念有了一个全新的认识。
通俗的解释:

可以将tcp和upd看成不同公司的出租车,tcp这个公司的出租车司机(tcp头)在拉客的时候,一看来了一个乘客,可是自己车上还有三个位置,司机就会继续等,直到自己车上去同一个目的地的乘客坐满了才开车,因为tcp公司认为遵循Nagle算法可以提高效率,节省能源,从socket学校走出来三个团体的学生,每一个团体只有一个人,可能只要消耗一个tcp出租车。如果从socket学校出来了一个团队的学生,但是这个团队有6个学生,一号tcp出租车看看自己车上还有两个个空位置,就让这个团队的两个学生上车了,剩下的学生只能做下一辆车了。这也就造成了一个问题,一号出租车开到了城市中的一个小餐馆,餐馆老板并不知道他们四个学生是不是一个团队的,这也就是粘包粘包的问题
粘包粘包的问题解决
1、发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。

2、发送端将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。

3、可以在数据包之间设置边界,如添加特殊符号,这样,接收端通过这个边界就可以将不同的数据包拆分开。

udp公司的出租车与tcp公司的出租车不一样,udp公司有最新的科技,自己的车可以变大可以变小。只要有一个团队的人走过来,不管是一个人还是7个人,udp出租车都可以一次性给你送走(因为udp有核心科技,当然下层的ip层还是可能会分包的,这些我们不用管),不需要等待。到餐馆后,餐馆老板一看是udp公司的出租车,就知道这是一个团队的(也就是不会出现粘包粘包的问题)。

tcp数据可靠性如何保证

TCP主要提供了检验和、确认应答与序列号、超时重传、最大消息长度、滑动窗口控制等方法实现了可靠性传输。
检验和
在发送方将整个报文段分为多个16位的段,然后将所有段进行相加取反,将结果存放在检验和字段中,接收方用相同的方法进行计算,如果检验和字段一致则正确,否则存在错误。
确认应答与序列号
TCP将每个数据包都进行了编号,这就是序列号。
序列号的作用:
a、保证可靠性(当接收到的数据总少了某个序号的数据时,能马上知道)
b、保证数据的按序到达
c、提高效率,可实现多次发送,一次确认
d、去除重复数据
数据传输过程中的确认应答处理、重发控制以及重复控制等功能都可以通过序列号来实现

TCP通过确认应答机制实现可靠的数据传输。在TCP的首部中有一个标志位——ACK,此标志位表示确认号是否有效。接收方对于按序到达的数据会进行确认,当标志位ACK=1时确认首部的确认字段有效。进行确认时,确认字段值表示这个值之前的数据都已经按序到达了。而发送方如果收到了已发送的数据的确认报文,则继续传输下一部分数据;而如果等待了一定时间还没有收到确认报文就会启动重传机制。
超时重传
TCP每发送一个报文段,就会对这个报文段设置一次计时器,只要计时器设置的重传时间到,但发送端还没有收到接收端发来的确认,此时就会重传此报文段

tcp滑动窗口

TCP的窗口滑动技术通过动态改变窗口的大小来调节两台主机之间数据传输。
每个TCP/IP主机支持全双工数据传输,因此TCP有两个滑动窗口,
一个用于接收数据,一个用于发送数据。接收方设备要求窗口大小为0时,表明接收方已经接收了全部数据,或者接收方应用程序没有时间读取数据
,要求暂停发送。
TCP在传送数据时,第一次接受方窗口大小是由链路带宽决定的,但是接收方在接收到的数据后,返回ack确认报文,同时也告诉了发送方自己的窗口大小,此时发送方第二次发送数据时,会改变自己的窗口大小和接收方一致。
当窗口过大时,会导致不必要的数据来拥塞我们的链路,但是窗口太小时,会造成很大的延时。

tcp/ip协议

TCP/IP传输协议,即传输控制/网络协议,也叫作网络通讯协议。它是在网络的使用中的最基本的通信协议。TCP/IP传输协议对互联网中各部分进行通信的标准和方法进行了规定。并且,TCP/IP传输协议是保证网络数据信息及时、完整传输的两个重要的协议。TCP/IP传输协议是严格来说是一个四层的体系结构,应用层、传输层、网络层和数据链路层都包含其中。
TCP/IP协议是Internet最基本的协议
应用层的主要协议有Telnet、FTP、SMTP等,是用来接收来自传输层的数据或者按不同应用要求与方式将数据传输至传输层
传输层的主要协议有UDP、TCP,提供了主机应用程序进程之间的端口到端口的服务。
网络层的主要协议有ICMP、IP、IGMP、ARP、RARP,根据网络地址将发出的数据包传送到目的网络地址
而网络访问层,也叫网络接口层或数据链路层,主要协议有PPP、SLE,主要功能是提供高质量无差错的数据链路连接(链路管理、流量控制 、差错控制、帧同步 )。
链路管理 :通信开始前,要建立数据链路连接;数据传输过程中,维护数据链路连接;通信结束后,释放数据链路连接。
差错控制 :将有差错的物理线路改进为无差错的数据链路,向网络层提供高质量的数据传输服务
OSI其余几层
表示层:对数据格式进行编译,对收到或发出的数据根据应用层的特征进行处理。
会话层:管理网络设备的会话连接
物理层:为数据端设备提供传送数据通路、传输数据。
注意:现在互联网使用的通信标准是五层结构的TCP/IP协议栈,在TCP/IP协议栈中的应用层包含了OSI七层模型里的应用层、表示层、会话层三层的作用。

tcp和udp应用

TCP应用场景:
效率要求相对低,但对准确性要求相对高的场景。因为传输中需要对数据确认、重发、排序等操作,相比之下效率没有UDP高。举几个例子:文件传输(准确高要求高、但是速度可以相对慢)、接受邮件、远程登录。

UDP应用场景:
效率要求相对高,对准确性要求相对低的场景。举几个例子:QQ聊天、在线视频、网络语音电话(即时通讯,速度要求高,但是出现偶尔断续不是太大问题,并且此处完全不可以使用重发机制)、广播通信(广播、多播)。

ACID

Atomicity(原子性):一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被恢复(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
Consistency(一致性):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
Isolation(隔离性):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。
Durability(持久性):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

Volatile

保持内存可见性

线程之间的可见性,一个线程修改的状态对另一个线程时可见的,也就是一个线程修改的结果,另一个线程马上就能看到

防止指令重排

volatile关键字通过“内存屏障”来防止指令被重排序。

注意事项

volatile关键字使变量的读、写具有了“原子性”。然而这种原子性仅限于变量(包括引用)的读和写,无法涵盖变量上的任何操作 在这里插入图片描述
在这里插入图片描述

JDK1.8 新特性

Lambda表达式:使用Lambda语法来代替匿名的内部类,代码不仅简洁,而且还可读。
Stream API:对容器对象功能的增强,它专注于对容器对象进行各种非常便利、高效的 聚合操作,或者大批量数据操作 。(map、filter、foreach)
hashmap改进:从数组+链表变成数组+链表+红黑树

事务的隔离性

基本概念

隔离性是指,多个用户的并发事务访问同一个数据库时,一个用户的事务不应该被其他用户的事务干扰,多个并发事务之间要相互隔离。

不考虑隔离性后果

1.脏读:
脏读是指一个事务在处理数据的过程中,读取到另一个为提交事务的数据。
在这里插入图片描述

事务1并没有提交,name 还是 lisi,但是事务2却读到了 name = wangwu,这就是脏读。如果换成A给B转账,B查询到了没有提交的事务,认为已经收到A转过来的钱,那岂不是很恐怖。
2.不可重复读:
不可重复读是指对于数据库中的某个数据,一个事务范围内的多次查询却返回了不同的结果,这是由于在查询过程中,数据被另外一个事务修改并提交了。
在这里插入图片描述
不可重复读和脏读的区别是,脏读读取到的是一个未提交的数据,而不可重复读读取到的是前一个事务提交的数据。

而不可重复读在一些情况也并不影响数据的正确性,比如需要多次查询的数据也是要以最后一次查询到的数据为主。
3.幻读
幻读是事务非独立执行时发生的一种现象。例如事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读。

幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。

不可重复读和幻读是初学者不易分清的概念,我也是看了详细的解读才明白的,总的来说,解决不可重复读的方法是 锁行,解决幻读的方式是 锁表。

四种隔离级别解决了上述问题

1.读未提交(Read uncommitted):

这种事务隔离级别下,select语句不加锁。

此时,可能读取到不一致的数据,即“读脏 ”。这是并发最高,一致性最差的隔离级别。

2.读已提交(Read committed):

可避免 脏读 的发生。

在互联网大数据量,高并发量的场景下,几乎 不会使用 上述两种隔离级别。

3.可重复读(Repeatable read):

MySql默认隔离级别。

可避免 脏读 、不可重复读 的发生。

4.串行化(Serializable ):

可避免 脏读、不可重复读、幻读 的发生。

实现原理

读未提交,它是性能最好,也可以说它是最野蛮的方式,因为它压根儿就不加锁,所以根本谈不上什么隔离效果,可以理解为没有隔离。
为了解决不可重复读,或者为了实现可重复读,MySQL 采用了 MVVC (多版本并发控制) 的方式。
可重复读是在事务开始的时候生成一个当前事务全局性的快照(readview),而读提交则是每次执行语句的时候都重新生成一次快照(readview)。
在通过readview查询出来的数据,都会剔除掉查询结果中属于上面已开启的事务版本的数据。(至于怎么剔除?别忘了事务版本是隐藏字段,mysql内部自然可以使用它筛选)
串行化,读的时候加共享锁,也就是其他事务可以并发读,但是不能写。写的时候加排它锁,其他事务不能并发写也不能并发读。

使用间隙锁加行锁的方式来防止当前读的幻读,在mysql中叫next key锁。
如果等值条件 值不存在的情况下加的是间隙锁,或者范围查询,加的也是间隙锁
RR隔离级别下为了解决“幻读”问题:“快照读”依靠MVCC控制,“当前读”通过间隙锁解决;

当前读和快照读

当前读
像lock in share mode(共享锁), for update(排它锁)这些操作都是一种当前读。

当前读就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁

快照读
Read View (主要是用来做可见性判断的):创建一个新事务时,copy一份当前系统中的活跃事务列表。意思是,当前不应该被本事务看到的其他事务id列表。
快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;

之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即MVCC,可以认为MVCC是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本

mvcc实现原理

每行记录除了我们自定义的字段外,还有数据库隐式定义的 记录创建修改的版本号和记录回滚的版本号
在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的 ID (当每个事务开启时,都会被分配一个 ID , 这个 ID 是递增的,所以最新的事务,ID 值越大)
当执行SQL语句查询时会产生一致性视图,也就是read-view,它是由查询的那一时间所有未提交事务ID组成的数组组成的。

在这个数组中最小的事务ID被称之为min_id,最大事务ID被称之为max_id,查询的数据结果要根据read-view做对比从而得到快照结果。

于是就产生了以下的对比规则,这个规则就是使用当前的记录的trx_id跟read-view进行对比,对比规则如下。
如果落在trx_id<min_id,表示此版本是已经提交的事务生成的,由于事务已经提交所以数据是可见的

如果落在trx_id>max_id,表示此版本是由将来启动的事务生成的,是肯定不可见的

若在min_id<=trx_id<=max_id时

如果row的trx_id在数组中,表示此版本是由还没提交的事务生成的,不可见,但是当前自己的事务是可见的
如果row的trx_id不在数组中,表明是提交的事务生成了该版本,可见

Java线程池

在这里插入图片描述
创建线程池最好不要使用Executors去创建,而是通过ThreadPoolExecutor的方式创建,通过这样子的创建方式,写的人就更加清楚线程池内部的运行规则,可以尽量避免内存溢出的情况。
SynchronousQueue没有容量,是无缓冲等待队列,是一个不存储元素的阻塞队列,会直接将任务交给消费者,必须等队列中的添加元素被消费后才能继续添加新的元素。使用SynchronousQueue阻塞队列一般要求maximumPoolSizes为无界(Integer.MAX_VALUE),避免线程拒绝执行操作。

执行流程

线程池中的执行流程:

(1)当线程数小于核心线程数的时候,使用核心线程数。

(2)如果核心线程数小于线程数,就将多余的线程放入任务队列(阻塞队列)中

(3)当任务队列(阻塞队列)满的时候,就启动最大线程数,新提交任务会创建新线程执行任务。

(4)当最大线程数也达到后,就将启动拒绝策略。

拒绝策略

有四种拒绝策略

1.ThreadPoolExecutor.AbortPolicy

线程池的默认拒绝策略为AbortPolicy,即丢弃任务并抛出RejectedExecutionException异常(即后面提交的请求不会放入队列也不会直接消费并抛出异常);

2.ThreadPoolExecutor.DiscardPolicy

丢弃任务,但是不抛出异常。如果线程队列已满,则后续提交的任务都会被丢弃,且是静默丢弃(也不会抛出任何异常,任务直接就丢弃了)。

3.ThreadPoolExecutor.DiscardOldestPolicy

丢弃队列最前面的任务,然后重新提交被拒绝的任务(丢弃掉了队列最前的任务,并不抛出异常,直接丢弃了)。

4.ThreadPoolExecutor.CallerRunsPolicy

由调用上层线程处理该任务(不会丢弃任务,最后所有的任务都执行了,并不会抛出异常)这提供了一个简单的反馈控制机制,将降低新任务提交的速度。

线程状态

“Java线程共有5中状态,分别为:新建(new)、就绪(runnable)、运行(running)、堵塞(blocked)、死亡(dead)。

spring

优点

(1)spring的DI机制将对象之间的依赖关系交由框架处理,减低组件的耦合性;

(2)Spring提供了AOP技术,支持将一些通用任务,如安全、事务、日志、权限等进行集中式管理,从而提供更好的复用。

(3)spring对于主流的应用框架提供了集成支持。

bean的线程安全

spring容器管理,默认情况下bean都是单例的。Javabean的两个概念:有状态bean与无状态bean。简单理解有状态bean就是在该bean创建的时候会携带一些特有的信息,这些信息会在程序执行时发生变动。而无状态bean就是在创建该bean时,他的状态就确定了,在后续的生命周期中不会发生变化。最简单的无状态bean就是没有任何字段的bean。spring boot中我们开发的controller、service、等基本上都是无状态bean。他们的特点是:只有方法,没有全局属性。我们定义的变量都在函数体内,这样的变量是每一个线程都会在自己的线程中创建不会共享的。所以这样的bean是线程安全的。所以我们在开发的时候要注意:bean中尽量不要定义全局属性,如果非要定义也要保证该属性是final的。
如何保证线程安全
使用@Scope(value=“prototype”)注解,将bean变成多例,所有的线程都使用各自创建的bean(这当然是不太好的,每个线程都创建bean的话内存岂不是要被撑爆?)。还有就是可以给bean添加锁机制,保证多个线程在操作同一个bean时是串行的。

特点

轻量、控制反转、面向切面、容器、框架集合

核心三大组件

Bean 作用:Bean的创建,Bean的定义,Bean的解析
Context 作用:在Spring中的context包下,为Spring提供运行环境,用以保存各个对象状态。
Core 作用:访问资源

IOC(控制反转)

IOC,Inversion of Control,控制反转,指将对象的控制权转移给Spring框架,由 Spring 来负责控制对象的生命周期(比如创建、销毁)和对象间的依赖关系。削减计算机程序的耦合(解除我们代码中的依赖关系)。

IOC结构

1.资源组件:Resource,对资源文件的描述,不同资源文件如xml、properties文件等,格式不同,最终都将被ResourceLoader加载获得相应的Resource对象;

2.资源加载组件:ResourceLoader:加载xml、properties等各类格式文件,解析文件,并生成Resource对象。

3.Bean容器组件:BeanFactory体系:IoC容器的核心,其他组件都是为它工作的(但不是仅仅为其服务).

4.Bean注册组件:SingletonBeanRegister/AliasRegister:将BeanDefinition对象注册到BeanFactory(BeanDefinition Map)中去。

5.Bean描述组件:BeanDefinition体系,Spring内部对Bean描述的基本数据结构

6.Bean构造组件:BeanDefinitionReader体系,读取Resource并将其数据转换成一个个BeanDefinition对象。

IOC流程

首先通过资源加载组件ResourceLoader加载不同格式配置文件,解析生成相应的资源组件Resource对象;然后通过Bean构造组件BeanDefinitionReader体系,读取Resource并将其数据转换成一个个Bean描述组件BeanDefinition对象。最后将BeanDefinition对象注册到BeanFactory(BeanDefinition Map)中去。

获得bean
1.注解
2.在这里插入图片描述

bean 装配过程

a. 加载配置文件,解析成 BeanDefinition 放在 Map 里。

b. 调用 getBean 的时候,从 BeanDefinition 所属的 Map 里,拿出 Class 对象进行实例化,同时,如果有依赖关系,将递归调用 getBean 方法 —— 完成依赖注入。

bean的生命周期

在spring框架中,所有的bean对象都有生命周期,就是指bean的创建、初始化、服务、销毁的一个过程。”
但在这过程之前进行了bean的注册,首先通过资源加载组件ResourceLoader加载不同格式配置文件,解析生成相应的Resource对象;然后通过Bean构造组件BeanDefinitionReader体系,读取Resource并将其数据转换成一个个Bean描述组件BeanDefinition对象。
最后将BeanDefinition对象注册到BeanFactory(BeanDefinition Map)中去。完成bean注册。
然后当bean创建时,Bean 容器Beanfactory找到配置文件中 Spring Bean 的定义(配置文件或注解的方式定义)
Bean 容器Beanfactory利用Java Reflection API创建一个Bean的实例(ApplicationContext或Beanfactory),如果涉及到一些属性值,利用set()方法设置一些属性值。如果 Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法。完成bean的创建和初始化。
容器关闭时,进行销毁 Bean,如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的方法。

DI依赖注入

IoC 的一个重点就是在程序运行时,动态的向某个对象提供它所需要的其他对象,这一点是通过DI(Dependency Injection,依赖注入)来实现的,即应用程序在运行时依赖 IoC 容器来动态注入对象所需要的外部依赖。而 Spring 的 DI 具体就是通过反射实现注入的,反射允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性

AOP(面向切面)

OOP面向对象,允许开发者定义纵向的关系,但并不适用于定义横向的关系,会导致大量代码的重复,而不利于各个模块的重用。
AOP,一般称为面向切面,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑的程序,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,提高系统的可维护性。可用于权限认证、日志、事务处理。

使用

@Aspect:用于定义一个切面,注解在切面类上
@Pointcut:用于定义切点表达式,在使用时,需要定义一个切入点方法,该方法返回值为void,且方法体为空的普通方法
@Before:用于定义前置通知,通常为其指定value属性,该值可以是已有切入点,也可以定义切点表达式
@AfterReturning:用于定义后置返回通知,通常为其指定value属性,该值可以是已有切入点,也可以定义切点表达式
@Around:用于定义环绕通知,通常为其指定value属性,该值可以是已有切入点,也可以定义切点表达式
@After:用于定义后置最终通知,通常为其指定value属性,该值可以是已有切入点,也可以定义切点表达式
带参数实例:https://blog.csdn.net/weixin_43931625/article/details/124367044
定义切入点的两种方式
1.注解
在这里插入图片描述
2.表达式
在这里插入图片描述

aop底层

实际上,AOP 的底层是通过 Spring 提供的的动态代理技术实现的。在运行期间,Spring通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入,在去调用目标对象的方法,从而完成功能的增强。
常用的动态代理技术
JDK 代理 : 基于接口的动态代理技术·:利用拦截器(必须实现invocationHandler)加上反射机制生成一个代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理,从而实现方法增强,继承了proxy类所以要通过接口实现。
CGLIB代理:基于父类的动态代理技术:动态生成一个要代理的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截技术拦截所有的父类方法的调用,顺势织入横切逻辑,对方法进行增强
在这里插入图片描述
因为 JDK 动态代理是基于接口的,代理生成的对象只能赋值给接口变量。
为了解决使用 JDK 动态代理可能导致的类型转化异常而默认使用 CGLIB。
spring boot默认使用CGLIB动态代理
spring.aop.proxy-target-class这个属性的值,默认为true表示强制使用Cglib动态代理
那么使用aop时实现了接口的类就会使用JDK动态代理,没有实现接口的类仍然使用的是Cglib动态代理。

cglib代理生成慢,调用快,jdk代理则相反。

bean作用域

(1)singleton:默认作用域,Spring 只会为每一个bean创建一个实例,并保持bean的引用.
(2)prototype:一个bean定义对应多个对象实例。每一次请求(将其注入到另一个bean中,或执行getBean()方法)都会产生一个新的bean实例,相当于new操作.

(3)request:request表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效,

(4)session:session作用域表示该针对每一次HTTP会话都会产生一个新的bean,同时该bean仅在当前HTTP session内有效
HTTP session是指会话,一次请求连接可能对应一个新的会话,也可以对应之前的已存在会话,因此同一会话中的多个请求中可以共享session中的数据。与request作用域一样,可以根据需要放心的更改所创建实例的内部状态,而别的 HTTP session 中根据 userPreferences 创建的实例,将不会看到这些特定于某个 HTTP session 的状态变化。

(5)global-session:全局作用域,所有会话共享一个实例,仅仅在基于 portlet 的 web 应用中才有意义。
Portlet是基于Java的Web组件,由Portlet容器管理,并由容器处理请求,生产动态内容。作为利用Servlets进行Web应用编程的下一步,Portlets实现了Web应用的模块化和用户中心化。

设计模式

工厂模式

定义一个工厂类,该类提供一个静态方法,该方法会根据传入的参数的不同来创建不同的实例。
BeanFactory。Spring中的BeanFactory就是简单工厂模式的体现,根据传入beanName来获得Bean对象,先从缓存中取,缓存中没有再创建。
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

策略模式

策略模式,接口抽象功能,具体的实现交给子类。不同的子类,实现的算法或者策略不一样。spring中在实例化对象的时候用到Strategy模式。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

单例模式

Spring中依赖注入的Bean实例默认是单例的。

Spring的依赖注入(包括lazy-init方式)都是发生在AbstractBeanFactory的getBean里。getBean的doGetBean方法调用getSingleton进行bean的创建。
懒汉模式
在这里插入图片描述

饿汉模式
在这里插入图片描述

加锁
在这里插入图片描述

双重校验锁
在这里插入图片描述

适配器模式

适配器模式(Adapter Pattern)将某个接口转换成客户端希望的另一个接口表示,主要目的是兼容性,让原本因接口不匹配而不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)
将一个类的接口转换成客户希望的另外一个接口,Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作
SpringMVC中的适配器HandlerAdatper。

DispatcherServlet根据HandlerMapping根据请求url查找handler,向HandlerAdatper发起请求,处理Handler。

HandlerAdapter根据规则找到对应的Handler并让其执行,执行完毕后Handler会向HandlerAdapter返回一个ModelAndView,最后由HandlerAdapter向DispatchServelet返回一个ModelAndView。

Spring创建了一个适配器接口(HandlerAdapter)使得每一种处理器(宽泛的概念Controller,以及HttpRequestHandler,Servlet,等等)有一种对应的适配器实现类,让适配器代替(宽泛的概念Controller,以及HttpRequestHandler,Servlet,等等)执行相应的方法。这样在扩展Controller 时,只需要增加一个适配器类就完成了SpringMVC的扩展了

// 使用5V充电
public interface Charge5V {
    void charging();
}

//充电宝类实现了该接口:

// 充电宝
public class MobilePower implements Charge5V {
    @Override
    public void charging() {
        System.out.println("使用充电宝给手机充电(5V)");
    }
}

然后手机可以充电:

// 手机
public class Phone {
    public void charge(Charge5V c) {
        c.charging();
    }

    public static void main(String[] args) {
        Phone phone = new Phone();
        MobilePower mobilePower = new MobilePower();
        phone.charge(mobilePower);
    }
}

//现在有一个插座类,输出220V电压

// 插座
public class Socket{
    public void chargingWith220V() {
        System.out.println("使用插座直接充电(220V)");
    }
}
座类也想给手机充电,但是因为接口类型不匹配,不能充电


//现在就需要出现一个适配器类:充电器

// 充电器(核心!!!)
public class ChargerAdapter extends Socket implements Charge5V {
    @Override
    public void charging() {
        chargingWith220V();
        System.out.println("转化为5V");
    }
}

然后插座通过使用充电器来给手机充电:

// 手机
public class Phone {
    public void charge(Charge5V c) {
        c.charging();
    }

    public static void main(String[] args) {
        Phone phone = new Phone();
        ChargerAdapter chargerAdapter = new ChargerAdapter();
        phone.charge(chargerAdapter);
    }
}

在这里插入图片描述

代理模式

代理模式给原对象提供了一个代理对象,代理对象可以访问原对象的引用,扩展功能,
AOP底层,就是动态代理模式的实现。
分为静态代理、动态代理、cglib代理
博客:https://blog.csdn.net/qq_35362572/article/details/122055715

装饰器模式

装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。

1.1、公共接口
public interface Car {
    void skin();
}
1.2、接口实现
实现汽车出厂色

public class Corolla implements Car {
 
    @Override
    public void skin() {
        System.out.println("超级无敌灰色");
    }
}
1.3、装饰器
装饰构件可以通过装饰器完成装饰

public abstract class FourS implements Car {
 
    private Car car;
 
    public FourS(Car car) {
        this.car = car;
    }
 
    @Override
    public void skin() {
        car.skin();
    }
}
1.4、装饰构件
喷枪红

public class RedSprayPaint extends FourS {
 
    public RedSprayPaint(Car car) {
        super(car);
    }
 
    @Override
    public void skin() {
        super.skin();
        System.out.println("花2000块钱喷红色的漆");
    }
}
喷枪白

public class WhiteSprayPaint extends FourS {
 
    public WhiteSprayPaint(Car car) {
        super(car);
    }
 
    @Override
    public void skin() {
        super.skin();
        System.out.println("花1000块钱喷白色的漆");
    }
}
1.5、测试装饰器
public class Main {
    public static void main(String[] args) throws IOException {
        System.out.println("====================出厂颜色====================");
        Car corolla = new Corolla();
        corolla.skin();
        System.out.println("====================换颜色====================");
        RedSprayPaint red = new RedSprayPaint(new WhiteSprayPaint(corolla));
        red.skin();
    }
}

模板模式

模板方法模式在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。用来解决代码重复的问题。RestTemplate, JmsTemplate, JpaTemplate。

观察者模式

分为观察者和被观察者,被观察者本身的状态改变时主动发出通知观察者。线程的notify wait。spring加载配置文件时通过监听器广播。

1、创建观察者接口Observer

public interface Observer {
//观察者接口
    void update(String msg);
}

2、创建被观察者接口

public interface Subject {
//被观察者的接口:注册、取消注册、观察通知
    void reg(Observer obs);
    void unreg(Observer obs);
    void sendMessage(String msg);
}

3、被观察者的实现类

public class Building implements  Subject {
    List<Observer> obses = new ArrayList<Observer>();
    //注册
    @Override
    public void reg(Observer obs) {
        obses.add(obs);
    }
    //取消注册 拉黑
    @Override
    public void unreg(Observer obs) {
        if (!obses.isEmpty()&&obses.contains(obs)){
            obses.remove(obs);
        }
    }
    //发消息,群发
    @Override
    public void sendMessage(String msg) {
        for (Observer obs : obses) {
            obs.update("某个售楼处的小姐姐:"+msg);
        }
    }
}

4、观察者的实现类

public class Yourself implements Observer{
    private String yourName;

    public Yourself(String yourName) {
        this.yourName = yourName;
    }

    @Override
    public void update(String msg) {
        System.out.println(yourName+"接到"+msg);
    }
}

5、main方法

public class App {
    public static void main(String[] args) {
        //创建一个售楼处的小姐姐(生产者)
        Subject sister = new Building();
        //创建3个观察者(消费者)
        Observer ys1 = new Yourself("张三");
        Observer ys2 = new Yourself("李四");
        Observer ys3 = new Yourself("王五");

        //去售楼处加小姐姐的微信
        sister.reg(ys1);
        sister.reg(ys2);
        sister.reg(ys3);

        //某天李四通知小姐姐取消订阅,说我不看了别发我了
        sister.unreg(ys2);

        //楼盘价格降了,售楼处的小姐姐通过加的微信通知观察者
        sister.sendMessage("楼房价格降低了");

    }
}

SpringCloud

5大组件

Eureka注册中心、Ribbon或feign负载均衡、Hystrix服务熔断、zull路由网关、Spring Cloud Config

java异常

常见异常

程序遇上了空指针、类型强制转换异常、数组下标越界、文件未找到异常、输入输出流异常、算术异常类。

运行时异常

在这里插入图片描述
异常继承关系

在这里插入图片描述

幂等性

幂等就是一个操作,不论执行多少次,产生的效果和返回的结果都是一样的

rocketMQ

优缺点

在这里插入图片描述

角色

在这里插入图片描述

hashtable、hashmap、ConcurrentHashMap、treemap的区别

HashTable:
(1)Hashtable的synchronized是针对整张Hash表的
(2)放入的key,value不能为空;
(3)线程安全的,所有方法均用synchronized修饰;
ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术,将数据分成一段一段的存储,给每一段数据配置一把锁,当一个线程占用锁访问其中一段数据时,其他段的数据也能被其它线程访问,采取 CAS 和 synchronized 来保证并发的安全性。在判断数组中当前位置为null的时候,使用CAS来把这个新的Node写入数组中对应的位置,当数组中的指定位置不为空时,通过加锁来添加这个节点进入数组(链表<8)或者是红黑树(链表>=8。synchronized 只锁定当前链表或红黑二叉树的首节点,这样只要 hash 不冲突,就不会产生并发问题。

HashMap:
(1)可以放入空值;
(2)基于hash表实现;
(3)线程不安全;
TreeMap :
(1)基于红黑树实现;
(2)是有序的存储
(3)自己可以定义排序器;

重写equals时要重写hashcode

在这里插入图片描述

hashmap底层

HashMap 是哈希表的 Map 接口的非同步实现。是基于数组+链表+红黑树来实现的。默认初始容量为 16,负载因子为 0.75 ,当元素个数超过容量的百分之75 的时候,就把数组的大小扩展为原来的2倍。当Map链表长度大于或等于阈值TREEIFY_THRESHOLD(默认为 8)的时候,如果同时还满足容量(数组的长度)大于或等于 MIN_TREEIFY_CAPACITY(默认为 64)的要求,就会把链表转换为红黑树。同样,后续如果由于删除或者其他原因调整了大小,当红黑树的节点小于或等于 6 个以后,又会恢复为链表形态。

hashmap

为什么大小是2的n次幂

HashMap是根据key的hash值决定key放到哪个桶中。rehash时的取余操作,hash % length == hash & (length - 1)这个关系只有在length等于二的幂次方时成立,位运算能比%高效得多。

hashmap put流程

1、put(key, value)中直接调用了内部的putVal方法,并且先对key进行了hash操作;
2、以HashMap索引数组表的长度减一与key的hash值进行与运算,得出在数组中的索引,如果索引指定的位置值为空,则新建一个k-v的新节点并记录;如果不是则说明索引指定的数组位置的已经存在内容,这个时候称之碰撞出现;
3.碰撞产生,判断key索引到的节点(暂且称作被碰撞节点)的hash、key是否和当前待插入节点(新节点)的一致,如果是一致的话,则先保存记录下该节点;
否则进入循环查找,循环中,先判断被碰撞节点的后继节点是否为空,为空则将新节点作为后继节点并记录,同时作为后继节点之后并判断当前链表长度是否超过最大允许链表长度8,如果大于的话并且前索引表长度大于64,进行转红黑树的操作;如果在一开始后继节点不为空,则先判断后继节点是否与新节点相同,相同的话就记录并跳出循环;如果两个条件判断都不满足则继续循环,直至进入某一个条件判断然后跳出循环;
4.最后,回到那个记录节点,将新节点的值写入,同时返回被碰撞节点的值,无碰撞产生返回null。

扩展
如果用B+树的话,在数据量不是很多的情况下,数据都会“挤在”一个结点里面。这个时候遍历效率就退化成了链表。
如果 hashCode 分布良好,也就是 hash 计算的结果离散好的话,那么红黑树这种形式是很少会被用到的,因为各个值都均匀分布,很少出现链表很长的情况。在理想情况下,桶(bins)中的节点数概率(链表长度)符合泊松分布,当桶中节点数(链表长度)为 8 的时候,概率仅为 0.00000006。这是一个小于千万分之一的概率,通常我们的 Map 里面是不会存储这么多的数据的,所以通常情况下,并不会发生从链表向红黑树的转换。

但是,HashMap 决定某一个元素落到哪一个桶里,是和这个对象的 hashCode 有关的,JDK 并不能阻止我们用户实现自己的哈希算法,如果我们故意把哈希算法变得不均匀, 事实上,链表长度超过 8 就转为红黑树的设计,更多的是为了防止用户自己实现了不好的哈希算法时导致链表过长,从而导致查询效率低,而此时转为红黑树更多的是一种保底策略,用来保证极端情况下查询的效率。

resize hashmap扩容流程

将hashmap容量变为原来的两倍,然后rehash通过hash值与原hashmap容量进行与运算(e.hash & oldCap) == 0来判断是否需要移位; 如果为真则在原位不动, 否则则需要移动到当前位置 + oldCap的位置;找到拆分后处于同一个桶的节点,将这些节点连接好,然后把头节点存入桶中即可。jdk1.7之前的扩容操作,如果涉及到多线程可能会出现链表循环的问题,原因是1.7版本采用遍历+头插的方法进行rehash,1.8则直接连接在两个子链表的末尾,也就避免了这个问题。1.8以后 是在链表转换树或者对树进行操作(重新平衡红黑树)的时候会出现死循环的问题。

负载因子为什么是0.75

负载因子0.75是为了尽量避免hash冲突的同时减少内存浪费。
table.length * 3/4可以被优化为(table.length >> 2) << 2) - (table.length >> 2) == table.length - (table.lenght >> 2), JAVA的位运算比乘除的效率更高, 所以取3/4在保证hash冲突小的情况下兼顾了效率;

属性

table:HashMap中存储数据的格式是内部声明的一个Node<K,V>类,而存储这些Node<K,V>是使用的数组。
entrySet:存储的键值对形成一个entrySet,用一个set集合存储。
size:指的是整个HashMap中存储的数据的个数。
modCount:由于HashMap是线程不安全的类,所以在操作HashMap中的数据时,会记录这个修改的次数,当使用迭代器遍历HashMap中的数据时,先把这个值赋给迭代器的expectedModCount,迭代的过程中比较这两个值,如果不相等直接抛异常,也就是源码注释中写的fail-fast机制。
threshold:扩容时的阈值。
loadFactor:加载因子,和容量的乘积就是阈值。

乐观锁和悲观锁

一:乐观锁(Optimistic Lock)

每次获取数据的时候,都不会担心数据会被修改,所以每次获取数据时都不会进行加锁。

但是在更新数据的时候,需要判断该数据是否被别人修改过,如果数据被其他线程修改过,则不进行数据更新。

如果数据没有被其他线程修改,则进行数据更新。由于数据没有进行加锁,期间该数据可以被其他线程进行读写操作。

二:悲观锁(Pessimistic Lock)

每次获取数据的时候,都会担心数据会被修改,所以每次获取数据的时候都会进行加锁,

确保在自己使用的过程中数据不被别人修改,使用完后进行数据解锁。

由于数据会进行加锁,期间对该数据进行读写和其他线程都会进行等待。

线程池的submit和excute

execute只能接受Runnable类型的任务
submit不管是Runnable还是Callable类型的任务都可以接受,
1.execute中抛出异常
execute中的是Runnable接口的实现,所以只能使用try、catch来捕获
即和普通线程的处理方式完全一致
2.submit中抛出异常
不管提交的是Runnable还是Callable类型的任务,如果不对返回值Future调用get()方法,出现异常不显示。

多线程

park/unpark与wait/notify的区别

park和unpark并不是线程的方法,而是LockSupport的静态方法

wait/notify必须在有锁的情况下使用(需要关联Monitor对象),park/unpark没有这个限制条件。
park/unpark配对使用能够精确的指定具体的线程的阻塞/运行,notify只能随机唤醒一个线程
park/unpark配对使用可以先unpark,wait/notify配合使用不能够先notify。

类的执行顺序

如果子类继承父类,则先执行父类的静态变量和静态代码块,再执行子类的静态变量和静态代码块。同样,接着在执行父类和子类非静态代码块和构造函数。

注意:(静态)变量和(静态)代码块的也是有执行顺序的,与代码书写的顺序一致。在(静态)代码块中可以使用(静态)变量,但是被使用的(静态)变量必须在(静态)代码块前面声明。

锁的升级和降级

无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁,且锁升级的顺序是不可逆的。

线程第一次获取锁获时锁的状态为偏向锁,如果下次还是这个线程获取锁,则锁的状态不变,否则会升级为CAS轻量级锁;如果还有线程竞争获取锁,如果线程获取到了轻量级锁没啥事了,如果没获取到会自旋,自旋期间获取到了锁没啥事,超过了10次还没获取到锁,锁就升级为重量级的锁,此时如果其他线程没获取到重量级锁,就会被阻塞等待唤起,此时效率就低了。

当 JVM 进入安全点(SafePoint)的时候,会检查是否有闲置的 Monitor,然后试图进行降级。

mysql增删改

INSERT INTO 表名 VALUES(值11,值2,…);
DELETE FROM 表名 [WHERE 条件表达式
UPDATE 表名 SET 字段名1=值1,[ ,字段名2=值2,…] [ WHERE 条件表达式 ]

http和https

概念
HTTP(超文本传输协议):是一个客户端和服务器端请求和应答的标准(TCP),用于从服务器传输超文本到本地浏览器的传输协议,它可以使浏览器更加高效,使网络传输减少。
HTTPS:是以安全为目标的HTTP通道,在HTTP的基础上加入SSL(安全套接字协议)层。

HTTPS和HTTP的区别主要如下:

1.、http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。

2、http和https用的端口也不一样,前者是80,后者是443。

3、http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

从输入URL到页面加载发生了什么

DNS(域名系统)解析

TCP连接

发送HTTP请求

服务器处理请求并返回HTTP报文

浏览器解析渲染页面

连接结束

get和post请求

GET请求在URL中传送的参数是有长度限制的,而POST没有。

GET请求会被浏览器主动cache,而POST不会,除非手动设置。

对参数的数据类型,GET只接受ASCII字符,而POST没有限制。

GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。

GET参数通过URL传递,POST放在Request body中。

面向对象

什么是面向对象

面向对象就是构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。

3大特性

封装、继承、多态
1、封装,是指隐藏对象的属性和实现细节,仅对外提供公共访问方式;
2、继承,从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力;
3、多态,一个方法可以有多种实现版本,即“一种定义, 多种实现”。

重写和重载

重载

(1)重载可以有不同的访问修饰符

(2)重载能够抛出不同的异常

(3)重载一定要有不同的参数列表

重写

(1)重写访问修饰符的限制一定要大于被重写方法的访问修饰符

(2)重写的参数列表一定要完全和被重写的方法相同,否则的话不能称其为重写而是重载
返回类型要小于(子类)或等于被重写的方法

(3)对于异常重写方法不能抛出新的异常或者比被重写方法声明的检查异常更广的检查异常。但是可以抛出更少,更有限或者不抛出异常。

mysql索引为什么快

此时可以理解为,将name这一列数据取出来,按照一定的方式重新组织一下(通常是b树或者散列),将name字段的每一个值,都与其所对应的表中那一条数据的地址做映射,使用哈希,一次就可以定位到该条数据。

synchronized修饰static和修饰方法区

一个锁class类一个锁实例。
类锁和对象锁不同,他们之间不会产生互斥。

wait()为什么要在同步块中

避免出现lost wake up问题
在这里插入图片描述

类加载时机和过程

时机

在这里插入图片描述

过程

加载、验证、准备、解析和初始化

1 加载

通过一个类的全限定名来获取定义此类的二进制字节流。

将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。

在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

2 验证

确保Class文件的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求,保证这些信息被当作代码运行后不会危害虚拟机自身的安全。

3 准备

准备阶段是正式为类中定义的变量(即静态变量,被static修饰的变量)分配内存并设置类变量初始值的阶段。

4 解析

Java虚拟机将常量池内的符号引用替换为直接引用的过程
什么是符号引用和直接引用
符号引用就是一个类中(当然不仅是类,还包括类的其他部分,比如方法,字段等),引入了其他的类,可是JVM并不知道引入的其他类在哪里,所以就用唯一符号来代替,等到类加载器去解析的时候,就把符号引用找到那个引用类的地址,这个地址也就是直接引用。

5 初始化

据程序员通过程序编码制定的主观计划去初始化类变量和其他资源。

Eureka和Zookeeper

CAP原则(C:强一致性。A:可用性。P:分区容错性)。
Eureka(保证AP),Zookeeper(保证CP)
当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的注册信息,但不能接受服务直接down掉不可用。也就是说,服务注册功能对可用性的要求要高于一致性。但是zk会出现这样一种情况,当master节点因为网络故障与其他节点失去联系时,剩余节点会重新进行leader选举。问题在于,选举leader的时间太长,30 ~ 120s, 且选举期间整个zk集群都是不可用的,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因网络问题使得zk集群失去master节点是较大概率会发生的事,虽然服务能够最终恢复,但是漫长的选举时间导致的注册长期不可用是不能容忍的。

Eureka看明白了这一点,因此在设计时就优先保证可用性。Eureka各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而Eureka的客户端在向某个Eureka注册或时如果发现连接失败,则会自动切换至其它节点,只要有一台Eureka还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。

生产者与消费者模型

生产者和消费者问题是线程模型中的经典问题:生产者和消费者在同一时间段内共用同一个存储空间,生产者往存储空间中添加产品,消费者从存储空间中取走产品,当存储空间为空时,消费者阻塞,当存储空间满时,生产者阻塞。

servlet生命周期

Servlet的生命周期分为5个阶段:加载、创建、初始化、处理客户请求、卸载。
(1)加载:容器通过类加载器使用servlet类对应的文件加载servlet
(2)创建:通过调用servlet构造函数创建一个servlet对象
(3)初始化:调用init方法初始化
(4)处理客户请求:每当有一个客户请求,容器会创建一个线程来处理客户请求
(5)卸载:调用destroy方法让servlet自己释放其占用的资源

mysql索引

聚簇索引和非聚簇索引

⾮叶⼦节点只会存储索引列和指向下级节点的指针
聚簇索引:按照每张表的主键构造一颗B+树,同时叶子节点中存放的就是表的行记录数据。每张表只能拥有一个聚簇索引。InnoDB存储引擎对每张表强制建了聚簇索引:当建了主键,自动以主键构建聚簇索引;没有主键时,自动选择一个可以唯一标识数据记录的列作为主键(唯一非空索引)。以上都不满足时,会自动生成一个长度为6字节的长整型隐含字段作为主键,作为聚簇索引。聚簇索引索引叶子节点key为主键value包含整行数据。
非聚簇索引:又叫辅助索引。按照每张表的索引构造一颗B+树,二级索引的叶子节点中保存行的主键值。每张表可以有多个辅助索引。当通过辅助索引查找行,存储引擎需要在辅助索引中找到相应的叶子节点,获得行的主键值,然后使用主键去聚簇索引中查找数据行,这需要两次B-Tree查找。

覆盖索引

1.覆盖索引是一种数据查询方式,不是索引类型
2.在索引数据结构中,通过索引值可以直接找到要查询字段的值,而不需要通过主键值回表查询,那么就叫覆盖索引
3.查询的字段被使用到的索引树全部覆盖到

怎么查看是否走索引

通过EXPLAIN语句来查看
explain返回参数详解:
id : 是select 的标识符,表示select 语句的执行顺序;如果该select语句有嵌套查询,则explain会有多行结果返回,也就会有多个id,id的数字越大表示该select子句越先执行;如果数字相同,则按从上到下的顺序执行;
table : 表名
select_type: select_type会根据不同的SQL语句返回不同的结果(simple primary union;)
type : 表示SQL语句的性能从好到差(system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL)
possible_keys: 查询可能使用到的索引都会在这里列出来
key:查询真正使用到的索引;
key_len: 用于处理查询的索引长度,如果是单列索引,那就是整个索引的长度;如果是多个索引,只会列出使用到了多少个列的索引,没有使用到的列不会计算进去;
ref:显示了之前的表在key列记录的索引中查找值所用的列或常量
rows: 执行计划估算的扫描行数,不是精确值;
extra: 额外信息
filtered:表示通过查询条件获取的最终记录行数占通过type字段指明的搜索方式搜索出来的记录行数的百分比。

不走索引(索引失效)原因

1、or条件中只要出现了非索引列,将会全表扫描
2、对于多列索引,不是包含第一部分,则不会使用索引。
3、like查询是以%开头,不会使用索引。
4、where 子句里对索引列上有数学运算或者函数,会导致索引失效而转向全表扫描
5.使用 in 或not in 或!=或 null 值或or判断都可能不走索引

索引

索引建立越多越好吗?

数据变更需要维护索引,因此更多的索引意味着更多的维护成本。
更多的索引意味着更多的存储空间。

MySQL存储引擎

在mysql数据库中,myisam引擎和innodb引擎使用的索引类型不同,myisam对应的是非聚簇索引,而innodb对应的是聚簇索引。聚簇索引也叫复合索引、聚集索引等等。
mysql-5.1版本之前默认引擎是MyISAM,之后是innoDB
  MyISAM是非集聚引擎,不支持事务;它是表级锁;会保存表的具体行数.
  innoDB是集聚引擎,支持事务;它是行级锁;不会保存表的具体行数.
  InnoDB表必须有唯一索引(如主键)(用户没有指定的话会自己找/生产 一个隐藏列Row_id来充当默认主键),而Myisam可以没有
  InnoDB支持外键,而MyISAM不支持。对一个包含外键的InnoDB表转为MYISAM会失败;
  InnoDB是聚集索引,使用B+Tree作为索引结构,数据文件是和(主键)索引绑在一起的(表数据文件本身就是按B+Tree组织的一个索引结构),必须要有主键,通过主键索引效率很高。但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。因此,主键不应该过大,因为主键太大,其他索引也都会很大。
MyISAM是非聚集索引,也是使用B+Tree作为索引结构,索引和数据文件是分离的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的。
  
一般:不用事务的时候,count计算多的时候适合myisam引擎。对可靠性要求高就是用innodby引擎。
在InnDB存储引擎中,每个辅助索引的每条记录都包含主键,也包含非聚簇索引指定的列。
innodb是聚簇索引,必须要有主键,一定会基于主键查询,但是辅助索引就会查询两次,索引b+树的叶子节点是存放数据的。myisam是非聚簇索引,索引和数据是分离的,索引里保存的是数据地址的指针,主键索引和辅助索引是分开的。

索引分类

1.普通索引index :加速查找
2.唯一索引
主键索引:primary key :加速查找+约束(不为空且唯一)
唯一索引:unique:加速查找+约束 (唯一)
3.联合索引
-primary key(id,name):联合主键索引
-unique(id,name):联合唯一索引
-index(id,name):联合普通索引
4.全文索引fulltext :用于搜索很长一篇文章的时候,效果最好。

索引存储结构

MySQL数据库索引存储结构一般有以下几种。 二叉树 红黑树 HASH B-Tree B+Tree(现在常用)。主要使用b+树作为索引存储结构

为什么选择B+树

1 B+树能显著减少IO次数,提高效率(非叶子节点不保存键值对应的数据,这样使得B+树每个节点所能保存的键值大大增加,树高度较低,查询效率高)
2 B+树的查询效率更加稳定,因为数据放在叶子节点
3 B+树能提高范围查询的效率,因为叶子节点指向下一个叶子节点

B+跟B树不同B+树的非叶子节点不保存键值对应的数据,这样使得B+树每个节点所能保存的键值大大增加;
B+树叶子节点保存了父节点的所有键值和键值对应的数据,每个叶子节点的关键字从小到大链接(这个特性对范围查找特别有利,范围查找只需要遍历链表即可,并不用像b树一样回旋查找,例如先查找5,再查找6,再查找7)
B+的非叶子节点只进行数据索引,不会存实际的键值对应的数据,所有数据必须要到叶子节点才能获取到,所以每次数据查询的次数都一样;

查找一个节点所做的IO次数是这个节点所处的树的高度,因为我们无法把整个索引都加载到内存,并且节点数据在磁盘中不是顺序排放的。所以最快情况下,磁盘的IO次数为数的高度。频繁的IO是阻碍提高性能的瓶颈,树高决定着IO的次数,那么降低树高不就能减少IO的次数吗,怎么减少呢,每个节点的数据多放一点不就行了,并且这个数据是存放在一块的,对应的是数据库中的读取的最小单位页,一次IO就可以将这些数据读取出来,虽然比较的次数有可能会增加,但是在内存中的比较和磁盘IO相比差几个数量级,整体上效率还是提高了

JAVA引用

强引用 ( Strong Reference ) 被强引用关联的对象永远不会被垃圾回收器回收掉
软引用( Soft Reference ) 软引用关联的对象,只有当系统将要发生内存溢出时,才会去回收软引用引用的对象
弱引用 ( Weak Reference ) 只被弱引用关联的对象,只要发生垃圾收集事件,就会被回收
虚引用 ( Phantom Reference ) 在任何时候都可能被垃圾回收。虚引用主要用来跟踪对象被垃圾回收的活动。被虚引用关联的对象的唯一作用是能在这个对象被回收器回收时收到一个系统通知。

threadlocal

它提供线程本地变量,如果创建一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题

ThreadLocal 是基于 ThreadLocalMap 实现的,其中 ThreadLocalMap 的 Entry 继承了 WeakReference ,而 Entry 对象中的 key 使用了 WeakReference 封装,也就是说, Entry 中的 key 是一个弱引用类型,对于弱引用来说,它只能存活到下次 GC 之前。而value是强引用。需要remove 防止了内存泄漏.
如果key是强引用的话,在表格中也能够看出来,被强引用关联的对象,永远都不会被垃圾回收器回收掉

如果引用的 ThreadLocal 对象被回收了,但是 ThreadLocalMap 还持有对 ThreadLocal 的强引用,如果没有 remove 的话, 在 GC 时进行可达性分析, ThreadLocal 依然可达,这样就不会对 ThreadLocal 进行回收,但是我们期望的是引用的 ThreadLocal 对象被回收,这样不就达不到目的了嘛
用法
设置创建一个
private static final ThreadLocal threadLocal = new ThreadLocal
1、ThreadLocal.get: 获取ThreadLocal中当前线程共享变量的值。

2、ThreadLocal.set: 设置ThreadLocal中当前线程共享变量的值。

3、ThreadLocal.remove: 移除ThreadLocal中当前线程共享变量的值。

rpc和http区别

相同点:底层通讯都是基于socket,都可以实现远程调用,都可以实现服务调用服务

不同点:
RPC:当使用RPC框架实现服务间调用的时候,要求服务提供方和服务消费方 都必须使用统一的RPC框架
跨操作系统在同一编程语言内使用
优势:调用快、处理快

http:当使用http进行服务间调用的时候,无需关注服务提供方使用的编程语言,也无需关注服务消费方使用的编程语言,服务提供方只需要提供接口,服务消费方请求服务,即可

跨系统跨编程语言
优势:通用性强

Redis缓存和Mysql数据库如何保证数据一致性

读取缓存步骤一般没有什么问题,但是一旦涉及到数据更新:数据库和缓存更新,就容易出现缓存(Redis)和数据库(MySQL)间的数据一致性问题。因为写和读是并发的,没法保证顺序,就会出现缓存和数据库的数据不一致的问题。

无论是“先删除缓存,再写库”,还是“先写MySQL数据库,再删除Redis缓存”,都有可能出现数据不一致的情况:

“先删除缓存,再写库” – 如果删除了缓存Redis,还没有来得及写库MySQL,另一个线程就来读取,发现缓存为空,则去数据库中读取数据写入缓存,此时缓存中为脏数据。
“先写MySQL数据库,再删除Redis缓存” – 如果先写了库,在删除缓存前,写缓存的线程宕机了,没有删除掉缓存,则也会出现数据不一致情况

方案一

延时双删策略
1.先删除缓存
2.再写数据库 – 在1步骤后,2步骤前,可能有“读”请求因缓存未命中,读到脏数据,再次将脏数据存入缓存
3.休眠500毫秒
4.再次删除缓存 – 删除可能在1-2步之间,被存入的脏数据

方案二

异步更新缓存
a) Redis更新数据操作,主要分为两大块:

一个是全量(将全部数据一次写入到redis)
一个是增量(实时更新)-- 这里的增量,指的是mysql的update、insert、delate变更数据
b) 读取binlog后分析 ,利用消息队列,推送更新各台的redis缓存数据。

这样一旦MySQL中产生了新的写入、更新、删除等操作,就可以把binlog相关的消息推送至Redis,Redis再根据binlog中的记录,对Redis进行更新。
其实这种机制,很类似MySQL的主从备份机制,因为MySQL的主备也是通过binlog来实现的数据一致性。
这里可以结合使用canal(阿里的一款开源框架),通过该框架可以对MySQL的binlog进行订阅,而canal正是模仿了mysql的slave数据库的备份请求,使得Redis的数据更新达到了相同的效果。
当然,这里的消息推送工具你也可以采用别的第三方:kafka、rabbitMQ等来实现推送更新Redis。

redis的读写缓存模式

旁路缓存模式
常用的一个缓存模式,适合读请求比较多的场景,同时维持缓存和数据库,以数据库结果为准
读过程: 先从缓存中读数据,读不到的话从数据库中找数据,然后把数据库读到的数据放入缓存
写过程:先更新数据库,后删除cache
读写穿透模式、异步缓存写入

写数据的过程,可以先删除缓存,然后更新数据库吗?

先删除缓存,然后更新数据库

请求A进行写操作,删除缓存
请求B查询发现缓存不存在
请求B去数据库查询得到旧值
请求B将旧值写入缓存
请求A将新值写入数据库

会较大概率出现脏数据

最好不要

1.缓存刚好失效
2.请求A查询数据库,得一个旧值
3.请求B将新值写入数据库
4.请求B删除缓存
5.请求A将查到的旧值写入缓存
ok,如果发生上述情况,确实是会发生脏数据。

然而,发生这种情况的概率又有多少呢?

发生上述情况有一个先天性条件,就是步骤(3)的写数据库操作比步骤(2)的读数据库操作耗时更短,才有可能使得步骤(4)先于步骤(5)。可是,大家想想,数据库的读操作的速度远快于写操作的(不然做读写分离干嘛,做读写分离的意义就是因为读操作比较快,耗资源少),因此步骤(3)耗时比步骤(2)更短,这一情形很难出现。

先更新数据库,再删缓存依然会出现脏数据,不过,问题出现的可能性会因为上面说的原因,变得比较低!

mysql日志

一、重做日志(redo log)
当有一条记录需要更新的时候,InnoDB引擎就会先把记录写到redo log(粉板)里面,并更新内存,这个时候更新就算完成了。
作用:

确保事务的持久性。防止在发生故障的时间点,尚有脏页未写入磁盘,在重启mysql服务的时候,根据redo log进行重做,从而达到事务的持久性这一特性。

二、回滚日志(undo log)
作用:

保存了事务发生之前的数据的一个版本,可以用于回滚,同时可以提供多版本并发控制下的读(MVCC),也即非锁定读

三、二进制日志(binlog):
作用:
用于复制,在主从复制中,从库利用主库上的binlog进行重播,实现主从同步。
用于数据库的基于时间点的还原。

session和cookie区别

1、存储位置不同
cookie的数据信息存放在客户端浏览器上。

session的数据信息存放在服务器上。

2、存储容量不同
单个cookie保存的数据<=4KB,一个站点最多保存20个Cookie。

对于session来说并没有上限,但出于对服务器端的性能考虑,session内不要存放过多的东西,并且设置session删除机制。

3、存储方式不同
cookie中只能保管ASCII字符串,并需要通过编码方式存储为Unicode字符或者二进制数据。

session中能够存储任何类型的数据,包括且不限于string,integer,list,map等。

4、隐私策略不同
cookie对客户端是可见的,别有用心的人可以分析存放在本地的cookie并进行cookie欺骗,所以它是不安全的。

session存储在服务器上,对客户端是透明对,不存在敏感信息泄漏的风险。

5、有效期上不同
sssion被访问的话 有效时间会重新刷新,默认30分钟
cookie默认关闭浏览器就消失,有设置时长则在设置时间内存活有效

6、服务器压力不同
cookie保管在客户端,不占用服务器资源。对于并发用户十分多的网站,cookie是很好的选择。

session是保管在服务器端的,每个用户都会产生一个session。假如并发访问的用户十分多,会产生十分多的session,耗费大量的内存。

token
token 验证是无状态的,服务器不记录哪些用户登录了或者哪些 JWT 被发布了,而是每个请求都带上了服务器需要验证的 token,token 放在了 Authorization header 中,形式是 Bearer { JWT },但是也可以在 post body 里发送,甚至作为 query parameter。

死锁

什么是死锁

线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。

如何避免

加锁顺序
加锁时限
死锁检测

死锁产生条件

死锁产生条件:互斥条件、不可剥夺条件、请求与保持条件、循环等待条件

解除死锁

死锁解除的主要方法有:

  1. 资源剥夺法。挂起某些死锁进程,并抢占它的资源,将这些资源分配给其他的死锁进程。但应防止被挂起的进程长时间得不到资源,而处于资源匮乏的状态。
  2. 撤销进程法。强制撤销部分、甚至全部死锁进程并剥夺这些进程的资源。撤销的原则可以按进程优先级和撤销进程代价的高低进行。
  3. 进程回退法。让一(多)个进程回退到足以回避死锁的地步,进程回退时自愿释放资源而不是被剥夺。要求系统保持进程的历史信息,设置还原点。

jdk和jre

JRE顾名思义是java运行时环境,包含了java虚拟机,java基础类库。是使用java语言编写的程序运行所需要的软件环境,是提供给想运行java程序的用户使用的。
JDK顾名思义是java开发工具包,是程序员使用java语言编写java程序所需的开发工具包,是提供给程序员使用的。JDK包含了JRE,同时还包含了编译java源码的编译器javac,还包含了很多java程序调试和分析的工具:jconsole,jvisualvm等工具软件,还包含了java程序编写所需的文档和demo例子程序。
JAVA的程序也就是我们编译的代码都会编译为Class文件,Class文件就是在JVM上运行的文件,

只有JVM还不能成class的执行,因为在解释class的时候JVM需要调用解释所需要的类库lib,而jre包含lib类库。

JVM屏蔽了与具体操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。

Hash索引和B+tree索引

区别

1、在查询速度上,如果是等值查询,那么Hash索引明显有绝对优势。
2、Hash 索引是无序的,如果是范围查询检索,这时候 Hash 索引就无法起到作用,即使原先是有序的键值,经过 Hash 算法后,也会变成不连续的了。
3、因为存在哈希碰撞问题,在有大量重复键值情况下,哈希索引的效率极低。B+tree 所有查询都要找到叶子节点,性能稳定;

场景区分

  1、大多数场景下,都会有组合查询,范围查询、排序、分组、模糊查询等查询特征,Hash 索引无法满足要求,建议数据库使用B+树索引。

  2、在离散型高,数据基数大,且等值查询时候,Hash索引有优势

动态代理和反射

动态代理
动态代理就是,在程序运行期,创建目标对象的代理对象,并对目标对象中的方法进行功能性增强的一种技术。

反射
主要是指程序可以访问、检测和修改它本身状态或行为的一种能力
在Java运行时环境中,对于任意一个类,能知道这个类有哪些属性和方法,对于任意一个对象,能调用它的任意一个方法

进程和线程

区别

1.进程是并发执行的程序在执行过程中分配和管理资源的基本单位,而线程是进程的一个执行单元,是进程内部调度实体。一个程序至少一个进程,一个进程至少一个线程。进程是资源的拥有者,线程是资源的调度者;
2.同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。同一进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的资源是独立的
3.一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。

通讯同步

线程间

1.锁机制:包括互斥锁、条件变量、读写锁
2.信号量机制(Semaphore):包括无名线程信号量和命名线程信号量
3.信号机制(Signal): 类似进程间的信号处理

进程间

1、信号 ( signal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

2、信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。
3、管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
4.共享内存( sharedmemory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。

序列化和反序列化

序列化:将 Java 对象转换成字节流的过程。

反序列化:将字节流转换成 Java 对象的过程。

现在微服务很流行,各个服务之间调用对象就需要把对象序列化用于在网络上传输。
当 Java 对象需要在网络上传输 或者 持久化存储到文件中时,就需要对 Java 对象进行序列化处理。

maven依赖冲突解决

1.在导入依赖时用exclusions去掉冲突依赖
2 显示导入

mybaits

1级缓存和2级缓存

一级缓存
Mybatis的一级缓存是指Session缓存。一级缓存的作用域默认是一个SqlSession。Mybatis默认开启一级缓存。
也就是在同一个SqlSession中,执行相同的查询SQL,第一次会去数据库进行查询,并写到缓存中;
第二次以后是直接去缓存中取。当执行SQL查询中间发生了增删改的操作,MyBatis会把SqlSession的缓存清空。
当Mybatis整合Spring后,直接通过Spring注入Mapper的形式,如果不是在同一个事务中每个Mapper的每次查询操作都对应一个全新的SqlSession实例,这个时候就不会有一级缓存的命中,但是在同一个事务中时共用的是同一个SqlSession。如有需要可以启用二级缓存。
二级缓存
Mybatis的二级缓存是指mapper映射文件。二级缓存的作用域是同一个namespace下的mapper映射文件内容,多个SqlSession共享。Mybatis需要手动设置启动二级缓存。

如何获取新插入数据在数据库中的id

每保存一条数据怎么将数据的保存后的id传入前端。
主要是在mybatis中加入 useGeneratedKeys=“true” keyProperty=“id”
mysql中使用获得最后插入id select LAST_INSERT_ID()

二级缓存使用规则

1.在一个命名空间下使用二级缓存
由于二级缓存中的数据是基于namespace的,即不同namespace中的数据互不干扰。在多个namespace中若均存在对同一个表的操作,那么这多个namespace中的数据可能就会出现不一致现象。
2.查询多于修改时使用二级缓存
在查询操作远远多于增删改操作的情况下可以使用二级缓存。因为任何增删改操作都将刷新二级缓存,对二级缓存的频繁刷新将降低系统性能。

#$

但是 #{} 和 ${} 在预编译中的处理是不一样的。
 #{} 在预处理时,会把参数部分用一个占位符 ? 代替,
 而 ${} 则只是简单的字符串替换
 优先使用 #{}。因为 ${} 会导致 sql 注入的问题
 其原因就是:采用了JDBC的PreparedStatement,就会将sql语句:“select * from student where uid= ? AND student_name= ?” 预先编译好,也就是SQL引擎会预先进行语法分析,产生语法树,生成执行计划,也就是说,后面你输入的参数,无论你输入的是什么,都不会影响该SQL语句的语法结构了,因为语法分析已经完成了,而语法分析主要是分析sql命令,比如select,from,where,and,or,order by等等。所以即使你后面输入了这些sql命令,也不会被当成sql命令来执行了,因为这些SQL命令的执行,必须先得通过语法分析,生成执行计划,既然语法分析已经完成,已经预编译过了,那么后面输入的参数,是绝对不可能作为SQL命令来执行的,只会被当做字符串字面值参数。所以的sql语句预编译可以防御SQL注入。而且在多次执行同一个SQL时,能够提高效率。原因是SQL已编译好,再次执行时无需再编译。

redis

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

缓存穿透
缓存穿透是指查询一个根本不存在的数据,缓存层和持久层都不会命中。在日常工作中出于容错的考虑,如果从持久层查不到数据则不写入缓存层,缓存穿透将导致不存在的数据每次请求都要到持久层去查询,失去了缓存保护后端持久的意义
缓存空对象 布隆过滤器拦截
缓存雪崩
由于缓存层承载着大量请求,有效地保护了存储层,但是如果缓存层由于某些原因不可用(宕机)或者大量缓存由于超时时间相同在同一时间段失效(大批key失效/热点数据失效),大量请求直接到达存储层,存储层压力过大导致系统雪崩。
可以把缓存层设计成高可用的集群,即使个别节点、个别机器、甚至是机房宕掉,依然可以提供服务。利用sentinel或cluster实现。
采用多级缓存,本地进程作为一级缓存,redis作为二级缓存,不同级别的缓存设置的超时时间不同,即使某级缓存过期了,也有其他级别缓存兜底
缓存的过期时间用随机值,尽量让不同的key的过期时间不同(例如:定时任务新建大批量key,设置的过期时间相同)
缓存击穿
在缓存失效的瞬间,有大量线程来重建缓存,造成后端负载加大,甚至可能会让应用崩溃。
分布式互斥锁
只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存获取数据即可。
永不过期

淘汰策略

当 Redis 内存超出物理内存限制时,内存的数据会开始和磁盘产生频繁的交换 (swap)。交换会让 Redis 的性能急剧下降,对于访问量比较频繁的 Redis 来说,这样龟速的存取效率基本上等于不可用。
在生产环境中我们是不允许 Redis 出现交换行为的,为了限制最大使用内存,Redis 提供了配置参数 maxmemory 来限制内存超出期望大小。
当实际内存超出 maxmemory 时,Redis 提供了几种可选策略 (maxmemory-policy) 来让用户自己决定该如何腾出新的空间以继续提供读写服务。

在这里插入图片描述
noeviction 不会继续服务写请求 (DEL 请求可以继续服务),读请求可以继续进行。这样可以保证不会丢失数据,但是会让线上的业务不能持续进行。这是默认的淘汰策略。
volatile-ttl:表示在设置可过期时间的键值对中,根据过期时间的先后进行淘汰数据,越早被过期的数据,越先被淘汰。
volatile-random:从名字可以看出来,就是在设置了过期时间的键值对中,随机淘汰数据。
volatile-lru:表示在设置可过期时间的键值对中,会根据 lru 算法进行数据的淘汰
allkeys-random:在全部的键值对数据中,进行数据的随机淘汰。
allkeys-lru:在全部的键值对数据中,根据 lru 算法进行数据的淘汰。
allkeys-lfu:在全部的键值对数据中,根据 lfu 算法进行数据的淘汰。
补充
LRU (Least recently used) 最近最少使用,如果数据最近被访问过,那么将来被访问的几率也更高。
LFU (Least frequently used) 最不经常使用,如果一个数据在最近一段时间内使用次数很少,那么在将来一段时间内被使用的可能性也很小。

zset底层

编码选择

有序集合对象的编码可以是ziplist或者skiplist。同时满足以下条件时使用ziplist编码:

元素数量小于128个
所有member的长度都小于64字节

ziplist

ziplist 编码的 Zset 使用紧挨在一起的压缩列表节点来保存,第一个节点保存 member,第二个保存 score。ziplist 内的集合元素按 score 从小到大排序,其实质是一个双向链表。虽然元素是按 score 有序排序的, 但对 ziplist 的节点指针只能线性地移动,所以在 REDIS_ENCODING_ZIPLIST 编码的 Zset 中, 查找某个给定元素的复杂度为 O(N)。

skiplist

skiplist 编码的 Zset 底层为一个被称为 zset 的结构体,这个结构体中包含一个字典(dict)和一个跳跃表。跳跃表按 score 从小到大保存所有集合元素,查找时间复杂度为平均 O(logN),最坏 O(N) 。跳表,是基于链表实现的一种类似“二分”的算法。字典则保存着从 member 到 score 的映射,这样就可以用 O(1)的复杂度来查找 member 对应的 score 值。虽然同时使用两种结构,但它们会通过指针来共享相同元素的 member 和 score,因此不会浪费额外的内存。

跳跃表 与平衡树、哈希表的比较

跳表,是基于链表实现的一种类似“二分”的算法
skiplist和各种平衡树(如AVL、红黑树等)的元素是有序排列的,而哈希表不是有序的。因此,在哈希表上只能做单个key的查找,不适宜做范围查找。所谓范围查找,指的是查找那些大小在指定的两个值之间的所有节点。
在做范围查找的时候,平衡树比skiplist操作要复杂。在平衡树上,我们找到指定范围的小值之后,还需要以中序遍历的顺序继续寻找其它不超过大值的节点。如果不对平衡树进行一定的改造,这里的中序遍历并不容易实现。而在skiplist上进行范围查找就非常简单,只需要在找到小值之后,对第1层链表进行若干步的遍历就可以实现。
平衡树的插入和删除操作可能引发子树的调整,逻辑复杂,而skiplist的插入和删除只需要修改相邻节点的指针,操作简单又快速。
从内存占用上来说,skiplist比平衡树更灵活一些。一般来说,平衡树每个节点包含2个指针(分别指向左右子树),而skiplist每个节点包含的指针数目平均为1/(1-p),具体取决于参数p的大小。如果像Redis里的实现一样,取p=1/4,那么平均每个节点包含1.33个指针,比平衡树更有优势。
查找单个key,skiplist和平衡树的时间复杂度都为O(log n),大体相当;而哈希表在保持较低的哈希值冲突概率的前提下,查找时间复杂度接近O(1),性能更高一些。所以我们平常使用的各种Map或dictionary结构,大都是基于哈希表实现的。
从算法实现难度上来比较,skiplist比平衡树要简单得多。
高度值
按照随机算法生成一个高度值
查找
·如果发现currentNode后继节点的值小于待查询值,则沿着这条链表向后查询,否则,切换到当前节点的下一层链表。
·继续查询,直到找到待查询值或者currentNode为空节点为止。

list底层

列表对象的编码可以是ziplist或者linkedlist。

ziplist是一种压缩链表,它的好处是更能节省内存空间,因为它所存储的内容都是在连续的内存区域当中的。当列表对象元素不大,每个元素也不大的时候,就采用ziplist存储。但当数据量过大时就ziplist就不是那么好用了。因为为了保证他存储内容在内存中的连续性,插入的复杂度是O(N),即每次插入都会重新进行realloc。如下图所示,对象结构中ptr所指向的就是一个ziplist。整个ziplist只需要malloc一次,它们在内存中是一块连续的区域。
linkedlist是一种双向链表。它的结构比较简单,节点中存放pre和next两个指针,还有节点相关的信息。当每增加一个node的时候,就需要重新malloc一块内存。

hash底层

哈希对象的底层实现可以是ziplist或者hashtable。

ziplist中的哈希对象是按照key1,value1,key2,value2这样的顺序存放来存储的。当对象数目不多且内容不大时,这种方式效率是很高的。

hashtable的是由dict结构来实现的
dict是一个字典,其中的指针dicht ht[2] 指向了两个哈希表
dicht[0] 是用于真正存放数据,dicht[1]一般在哈希表元素过多进行rehash的时候用于中转数据。
dictht中的table用语真正存放元素了,每个key/value对用一个dictEntry表示,放在dictEntry数组中。

set底层

Redis中的set和java中的set集合有相似之处,它的元素不会按照插入的向后顺序而存储,且元素是不允许重复的。set内部使用到了intset(整数集合)和hashtable(哈希表)两种方式来存储元素,如果set存储的元素是整数,且当元素个数小于512个会选择intset存储,目的是减少内存空间,遇到两种情况会发生变化,就是当存储的元素个数达到512(通过set-max-intset-entries 配置)或者添加了非整数值时如:‘b’,set会选择hashtable作为存储结构。

string底层

源码规定字符串长度不能超过 512M
int 编码:保存的是可以用 long 类型表示的整数值。
raw 编码:保存长度大于44字节的字符串(redis3.2版本之前是39字节,之后是44字节)
embstr 编码:保存长度小于44字节的字符串(redis3.2版本之前是39字节,之后是44字节)
embstr 使用只分配一次内存空间(因此redisObject和sds是连续的),raw 需要分配两次内存空间(分别为redisObject和sds分配空间)
因此与raw相比,embstr的好处在于创建时少分配一次空间,删除时少释放一次空间,以及对象的所有数据连在一起,寻找方便。而embstr的坏处也很明显,如果字符串的长度增加需要重新分配内存时,整个redisObject和sds都需要重新分配空间,因此redis中的embstr实现为只读。
Redis 的字符串是动态字符串,是可以修改的字符串,叫着“SDS”,也就是 Simple Dynamic String。
    所有的 Redis 对象 都有RedisObject 对象头结构 .

redis的总的数据结构

redis 中的每一个数据库,都由一个 redisDb 的结构存储。其中,redisDb.id 存储着 redis 数据库的号码(以整数表示)。redisDb.dict 存储着该库所有的键值对数据。redisDb.expires 保存着每一个键的过期时间。

与mysql区别

1、mysql支持sql查询,可以实现一些关联的查询以及统计;

2、redis对内存要求比较高,在有限的条件下不能把所有数据都放在redis;

3、mysql偏向于存数据,redis偏向于快速取数据,但redis查询复杂的表关系时不如mysql,所以可以把热门的数据放redis,mysql存基本数据。

4.mysql用于持久化的存储数据到硬盘,功能强大,但是速度较慢

redis用于存储使用较为频繁的数据到缓存中,读取速度快

mybatis

执行SQL语句的三种配置方式

  1. 只使用xml配置文件
  2. 接口配合xml配置文件
  3. 使用注解的方法

java

自动装箱 自动拆箱

包装类的作用: Java 语言中,一切都是对象,但是有例外: 8 个基本数据类型不是对象,因此在很多时候非常不方便。 为此, Java提供为 8 个基本类型提供了对应的包装类。

自动装箱 ----- 基本类型的值 → 包装类的实例

自动拆箱 ----- 基本类型的值 ← 包装类的实例

在装箱的时候自动调用的是Integer的valueOf(int)方法。而在拆箱的时候自动调用的是Integer的intValue方法。

NIO

JAVA NIO:它是一种同步非阻塞的I/O模型,也是I/O多路复用的基础,已经被越来越多地应用到大型应用服务器,成为解决高并发与大量连接、I/O处理问题的有效方式。

NIO是一种基于通道和缓冲区的I/O方式,它可以使用Native函数库直接分配堆外内存(区别于JVM的运行时数据区),然后通过一个存储在java堆里面的DirectByteBuffer对象作为这块内存的直接引用进行操作。这样能在一些场景显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。

NIO主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector(选择器)。传统IO是基于字节流和字符流进行操作(基于流),而NIO基于Channel和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择区)用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个线程可以监听多个数据通道。

list移除元素注意

(1)循环删除list中特定一个元素的,可以使用增强for循环、for循环、迭代器iterator中的任意一种,但在使用中要注意remove后list大小改变用增强需要马上break。

(2)循环删除list中多个元素的,应该使用迭代器iterator方式。

springboot

启动流程(自动装配过程)

SpringBoot项目启动的时,会先导入AutoConfigurationImportSelector,通过这个类对所有候选的配置进行选择,这些配置类都是SpringBoot帮我们写好的一个一个的配置类,那么这些配置类的位置,存在与META-INF/spring.factories文件中,通过这个文件,Spring可以找到这些配置类的位置,但是不是所有存在于spring,factories中的配置都进行加载,而是通过@ConditionalOnClass注解进行判断条件是否是否引入相互的jar包,(只要导入相应的stater,条件就能成立),如果条件成立则加载配置类,否则不加载该配置类。于是去加载其中的配置。

拦截器

1.编写一个拦截器实现HandlerInterceptor接口
方法preHandle 目标方法执行之前执行
方法postHandle 目标方法执行完成以后,还未到页面渲染
方法afterCompletion 页面渲染以后执行,或者方法执行失败执行
2.拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors)
3、指定拦截规则【如果是拦截所有,静态资源也会被拦截】
在这里插入图片描述

加载配置文件

在项目启动后,进行运行环境初始化后会发布出一个事件给负责分发事件的监听器EventPublishingRunListener,该监听器通过广播的方式广播一个事件(包装了一个ApplicationEnvironmentPreparedEvent事件)给监听该事件的监听器ConfigFileApplicationListener,最后在该监听器中实例化了一个Loader用来加载application配置文件。

@Transactional事务注解参数详解

参数一: propagation 传播机制
1、 required(默认属性)
如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。
被设置成这个级别时,会为每一个被调用的方法创建一个逻辑事务域。如果前面的方法已经创建了事务,那么后面的方法支持当前的事务,如果当前没有事务会重新建立事务。
2、 Mandatory
支持当前事务,如果当前没有事务,就抛出异常。
3、 Never
以非事务方式执行,如果当前存在事务,则抛出异常。
4、 Not_supports
以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
5、 requires_new
新建事务,如果当前存在事务,把当前事务挂起。
6、 Supports
支持当前事务,如果当前没有事务,就以非事务方式执行。
7、Nested
支持当前事务,新增Savepoint点,与当前事务同步提交或回滚。嵌套(nested)事务内出现异常,嵌套事务不影响主事务的流程。主事务回滚嵌套也回滚。

参数二:事物超时设置: timeout
默认30秒
参数三:事务隔离级别:isolation
参数四: readOnly
属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。
参数五:rollbackFor
该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。默认值为UncheckedException,unchecked exception不只有RuntimeException及其子类,还有Error 及其子类。
参数八:noRollbackFor
该属性用于设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚。
默认情况下使用注意
**1.不同类之间的方法调用。**如类A的方法a()调用类B的方法b(),这种情况事务是正常起作用的。只要方法a()或b()配置了事务,运行中就会产生代理,开启事务。

注意,事务正常起作用是指注解的方法内事务操作生效,下面是两种典型场景实例:

如果是在A的方法a()上加@Transactional注解,那么先正常调用b()且b()有增删改操作,但是接着执行a()的动库操作异常,则两个方法内动库操作全部回滚;

如果A的方法a()没加@Transactional注解,而类B的方法b()上加了该注解,那么如果先正常执行中a()的动库操作,然后执行类B的方法b()的动库操作发生异常,那么b()中的事务操作会全部回滚,但是并不会影响先前a()中的操作,即b()中异常b()回滚,a()不回滚。

2.同一个类的不同方法之间的调用以同类中方法a()调用方法b()为例:

1).方法a()开启了事务,事务正常生效

2).方法a()没有开启事务,此时调用方法b(),无论被调用的b()是否配置了事务,事务都不会生效

同类中事物调用非事物方法 非事物中用事物操作没加事物注解是可以一起回滚的,此时受最外层的事物的影响 统一为一个事物。

shiro

Apache Shiro是Java的一个安全框架。功能强大,使用简单的Java安全框架,它为开发人员提供一个直观而全面的认证,授权,加密及会话管理的解决方案。shiro默认登录过期时间是30分钟。

Linux常用命令

ls 查看当前目录下的所有目录和文件
mkdir 创建目录
rm 删除目录(加个-r参数)或文件
cp 拷贝目录
mv 重命名目录
vi 编辑文件
pwd 查看当前目录
ps 查看进程
kill 结束进程
netstat -an 查看当前系统端口(显示网络连接、路由表和网络接口信息)
ping 查看与某台机器的连接情况
cd 目录切换

监控linux的系统状况命令

top命令经常用来监控linux的系统状况,是常用的性能分析工具,能够实时显示系统中各个进程的资源占用情况。

String

赋值存储

对于字符串:其对象的引用都是存储在栈中的,如果是编译期已经创建好(直接用双引号定义的)的就存储在常量池中,如果是运行期(new出来的)才能确定的就存储在堆中。

大小限制

字符串有长度限制吗?是多少?

首先字符串的内容是由一个字符数组 char[] 来存储的,由于数组的长度及索引是整数,且String类中返回字符串长度的方法length() 的返回值也是int ,所以通过查看java源码中的类Integer我们可以看到Integer的最大范围是2^31 -1
由于数组是从0开始的,所以数组的最大长度可以使【0~2^31】通过计算是大概4GB。

但是java虚拟机手册Class文件中常量池的格式规定了,其字符串常量的长度不能超过2个字节。2个字节可以表示的最大范围是2^16 -1 = 65535。

其实是65535,但是由于JVM需要1个字节表示结束指令,所以这个范围就为65534了。超出这个范围在编译时期是会报错的,但是运行时拼接或者赋值的话范围是在整形的最大范围。

使用final修饰

保证String对象的不可变
1.提高效率
String类经常作为哈希表中的key,经常要使用到其hash值,基于String不可变的特性,可以对其hash值进行缓存,减少重复运算,String类有一个成员变量hash,对hash值进行了缓存
2.提高资源的利用率
String是最常用的对象,为了节省内存,基于String不可变的特性,可以实现字符串常量池。
创建String对象前,jvm会先检查字符串常量池中是否存在该对象,若存在则直接返回其引用,否则新建一个对象并缓存进常量池,再返回引用。
字符串常量池避免了重复String对象的创建,节省了内存资源,同时由于减少了对象创建的次数,也提高了程序的执行效率
3.保证线程安全
String是不可变的,因此不必担心String对象会被其他线程改变,天生线程安全

红黑树

红黑树是一种自平衡二叉查找树。

与平衡二叉(查找)树区别

1、红黑树放弃了追求完全平衡,追求大致平衡,在与平衡二叉树的时间复杂度相差不大的情况下,保证每次插入最多只需要三次旋转就能达到平衡,实现起来也更为简单。
2、平衡二叉树左右两子树的追求绝对平衡,条件比较苛刻,实现起来比较麻烦,每次插入新节点之后需要旋转的次数不能预知。
红黑树从根到叶子的最长的可能路径不多于最短的可能路径的两倍长
平衡二叉树它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
二叉搜索树缺点
二叉排序树的查找是走根节点到目标节点的路径,最多不会超过树的深度。也就是说,二叉排序树的查找性能取决于二叉树的形状,但问题是,二叉排序树的形状是不确定的,会出现右斜树和左斜树。
红黑树:
在这里插入图片描述

红黑树特性

每个节点要么是红色,要么是黑色。

根节点永远是黑色的。

所有的叶子节点都是空节点(即null),并且是黑色的。

没有两个相邻的红色节点(红色节点不能有红色父节点或红色子节点)。

从任一节点到其子树中每个叶子节点的路径都包含相同数量的黑色节点。

变化规则

旋转和颜色变化规则
1、添加的节点必须为红色

2、变色的情况:当前结点的父亲是红色,且它的叔结点也是红色:

2.1 把父节点设置为黑色

2.2 把叔节点设置为黑色

2.3 把祖父节点设置为红色

2.4 把当前指针定义到祖父节点,设为当前要操作的

3、左旋的情况:当前父节点是红色,叔节点是黑色,且当前的节点是右子树。

3.1 以父节点作为左旋。

4、右旋的情况:当前父节点是红色,叔节点是黑色,且当前的节点是左子树。

4.1 把父节点变成黑色

4.2 把祖父节点变为红色

4.3 以祖父节点右旋转

object 有哪些方法

java的Object类方法如下:

1.getClass方法

获取运行时类型,返回值为Class对象
2.hashCode方法

返回该对象的哈希码值,是为了提高哈希表的性能(HashTable)

3.equals方法

判断两个对象是否相等,在Object源码中equals就是使用去判断,所以在Object中equals是等价于的,但是在String及某些类对equals进行了重写,实现不同的比较。

4.clone方法

主要是JAVA里除了8种基本类型传参数是值传递,其他的类对象传参数都是引用传递,我们有时候不希望在方法里将参数改变,这时就需要在类中复写clone方法。

如果在clone方法中调用super.clone()方法需要实现Cloneable接口,否则会抛出CloneNotSupportedException。

此方法只实现了一个浅层拷贝,对于基本类型字段成功拷贝,但是如果是嵌套对象,只做了赋值,也就是只把地址拷贝了,所以没有成功拷贝,需要自己重写clone方法进行深度拷贝。

5.toString方法

返回一个String字符串,用于描述当前对象的信息,可以重写返回对自己有用的信息,默认返回的是当前对象的类名+hashCode的16进制数字。

6.wait方法

多线程时用到的方法,作用是让当前线程进入等待状态,同时也会让当前线程释放它所持有的锁。直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,当前线程被唤醒

7.notify方法

多线程时用到的方法,唤醒该对象等待的某个线程

8.notifyAll方法

多线程时用到的方法,唤醒该对象等待的所有线程

9.finalize

对象在被GC释放之前一定会调用finalize方法,对象被释放前最后的挣扎,因为无法确定该方法什么时候被调用,很少使用。
为什么wait,notify,notifyAll定义在Object中
JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。如果wait()方法定义在Thread类中,线程正在等待的是哪个锁就不明显了。简单的说,由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中因为锁属于对象。

泛型机制

什么是泛型?
是一种把类型明确的工作推迟到创建对象或者调用方法的时候才去明确的特殊的类型
边界
1.无边界的通配符(Unbounded Wildcards), 就是<?>, 比如List<?>
无边界的通配符的主要作用就是让泛型能够接受未知类型的数据.
2 固定上边界的通配符(Upper Bounded Wildcards),采用<? extends E>的形式
使用固定上边界的通配符的泛型, 就能够接受指定类及其子类类型的数据。
3固定下边界的通配符(Lower Bounded Wildcards),采用<? super E>的形式
使用固定下边界的通配符的泛型, 就能够接受指定类及其父类类型的数据.。
注意: 你可以为一个泛型指定上边界或下边界, 但是不能同时指定上下边界。
泛型优点
1.将运行时期的问题提前到了编译期间

2.避免了强制类型转换

3.泛型只在编译期间有效,在运行期就擦除了

java多态

实现

Java实现多态:继承、重写、重载、向上转型。

静态的多态:方法名相同,参数个数或类型不相同。(overloading)
动态的多态:
子类覆盖父类的方法,将子类的实例传与父类的引用调用的是子类的方法
实现接口的实例传与接口的引用调用的实现类的方法。

原理

静态绑定

静态绑定 是在编译时就已经确定好具体调用方法的情况

动态绑定

多态允许具体访问时实现方法的动态绑定。Java对于动态绑定的实现主要依赖于方法表,通过继承和接口的多态实现有所不同。

继承:在执行某个方法时,在方法区中找到该类的方法表,再确认该方法在方法表中的偏移量,找到该方法后如果被重写则直接调用,否则认为没有重写父类该方法,这时会按照继承关系搜索父类的方法表中该偏移量对应的方法。

接口:Java 允许一个类实现多个接口,从某种意义上来说相当于多继承,这样同一个接口的的方法在不同类方法表中的位置就可能不一样了。所以不能通过偏移量的方法,而是通过搜索完整的方法表。

JUC 部分常用类

CountDownLatch

CountDownLatch count = new CountDownLatch(5);
count.countDown();
count.await();

CyclicBarrier

CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
System.out.println(“召唤神龙”);
});
cyclicBarrier.await();

Semaphore信号量 限流

Semaphore semaphore = new Semaphore(2);
semaphore.acquire();
semaphore.release();

ReadWriteLock 读写锁

readWriteLock.writeLock().lock();
readWriteLock.writeLock().unlock();
readWriteLock.readLock().lock();
readWriteLock.readLock().unlock();

mysql主要

连接

内连接、外连接、自连接
内连接:在每个表中找出符合条件的共有记录。
inner join…on
外连接有三种方式:左连接,右连接和全连接。
左连接:根据左表的记录,在被连接的右表中找出符合条件的记录与之匹配,如果找不到与左表匹配的,用null表示。
left join … on …
右连接:根据右表的记录,在被连接的左表中找出符合条件的记录与之匹配,如果找不到匹配的,用null填充。
right join … on…
全连接:返回符合条件的所有表的记录,没有与之匹配的,用null表示
full join … on …
自连接
自连接,连接的两个表都是同一个表,同样可以由内连接,外连接各种组合方式,按实际应用去组合。

删除

当表被TRUNCATE 后,这个表和索引所占用的空间会恢复到初始大小,

DELETE操作不会减少表或索引所占用的空间。

drop语句将表所占用的空间全释放掉。

DELETE语句执行删除的过程是每次从表中删除一行,并且同时将该行的删除操作作为事务记录在日志中保存以便进行进行回滚操作。

TRUNCATE 和DELETE只删除数据, DROP则删除整个表(结构和数据)。

TRUNCATE TABLE 则一次性地从表中删除所有的数据并不把删除操作记录记入日志保存,删除的行是不能恢复的。并且在删除的过程中不会激活与表有关的删除触发器。执行速度快。

表结构

auto_increment自增关键字
在这里插入图片描述

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

反向代理和正向代理

正向代理,架设在客户机与目标主机之间,只用于代理内部网络对Internet的连接请求,客户机必须指定代理服务器,并将本来要直接发送到Web服务器上的http请求发送到代理服务器中。

反向代理服务器架设在服务器端,通过缓冲经常被请求的页面来缓解服务器的工作量,将客户机请求转发给内部网络上的目标服务器;并将从服务器上得到的结果返回给Internet上请求连接的客户端,此时代理服务器与目标主机一起对外表现为一个服务器。

mysql主键选择

一般选择自增id作为主键:
数据库自动编号,速度快,而且是增量增长,聚簇型主键按顺序存放,对于检索非常有利。
数字型,占用空间小,易排序,在程序中传递方便。
索引底层使用B+树。
(如果是多个数据库自增id作为主键,uuid作为逻辑主键)
自增量的值都是需要在系统中维护一个全局的数据值,每次插入数据时即对此次值进行增量取值。当在产生唯一标识的并发环境中,每次的增量取值都必须为此全局值加锁解锁以保证增量的唯一性。造成并发瓶颈,降低查询性能。每创建一条记录都需要对表加一次锁,在高并发环境下开销较大。
Innodb中的每张表都会有一个聚集索引,自增主键会把数据自动向后插入,避免了插入过程中的聚集索引排序问题。聚集索引的排序,必然会带来大范围的数据的物理移动,这里面带来的磁盘IO性能损耗是非常大的。

http状态码

1XX:信息提示 1XX系列响应代码仅在与HTTP服务器沟通时使用。
2XX: 成功 2XX系列响应代码表明操作成功了。
3XX 请求被重定向 3XX系列响应代码表明:客户端需要做些额外工作才能得到所需要的资源。它们通常用于GET请求。他们通常告诉客户端需要向另一个URI发送GET请求,才能得到所需的表示。那个URI就包含在Location响应报头里。
4XX:客户端错误 这些响应代码表明客户端出现错误。不是认证信息有问题,就是表示格式或HTTP库本身有问题。客户端需要自行改正。
5XX 服务端错误 这些响应代码表明服务器端出现错误。一般来说,这些代码意味着服务器处于不能执行客户端请求的状态,此时客户端应稍后重试。有时,服务器能够估计客户端应在多久之后重试。并把该信息放在Retry-After响应报头里。

200(“OK”)
一切正常。实体主体中的文档(若存在的话)是某资源的表示。

400 (错误请求) 服务器不理解请求的语法。

500 (服务器内部错误) 服务器遇到错误,无法完成请求。

301 (永久移动) 请求的网页已永久移动到新位置。 服务器返回此响应(对 GET 或 HEAD 请求的响应)时,会自动将请求者转到新位置

404(“Not Found”) 和410(“Gone”)
当客户端所请求的URI不对应于任何资源时,发送此响应代码。404用于服务器端不知道客户端要请求哪个资源的情况;410用于服务器端知道客户端所请求的资源曾经存在,但现在已经不存在了的情况。

409(“Conflict”)
当客户端试图执行一个”会导致一个或多个资源处于不一致状态“的操作时,发送此响应代码。
304
网站304的错误状态代码是当客户端试图访问服务器互相的信息提示。如果第二次访问期间页面内容没有更改,服务器将返回304状态代码。严格来说,这不是一个错误。
401 (未授权) 请求要求身份验证。 对于需要登录的网页,服务器可能返回此响应。
参考
https://blog.csdn.net/weixin_39624700/article/details/111431207

synchronized底层

每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:

1、如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。

2、如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.

3.如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。

一致性hash

数据较多进行分库分表时用到。
一致性Hash算法也是使用取模的方法,不过,上述的取模方法是对服务器的数量进行取模,而一致性的Hash算法是对2的32方取模。即,一致性Hash算法将整个Hash空间组织成一个虚拟的圆环.将数据Key使用相同的函数Hash计算出哈希值,并确定此数据在环上的位置,从此位置沿环顺时针查找,遇到的服务器就是其应该定位到的服务器。
解决的问题:hash直接对服务器数量取模的话,Redis服务器变动时,所有缓存的位置都会发生改变。
优点
加入和删除节点只影响哈希环中顺时针方向的相邻的节点,对其他节点无影响。
缺点
数据的分布和节点的位置有关,因为这些节点不是均匀的分布在哈希环上的,所以数据在进行存储时达不到均匀分布的效果。所以,出现了增加虚拟节点的方式来减少不均衡的现象。

springMVC

SpringMVC框架是以请求为驱动,围绕Servlet设计,将请求发给控制器,然后通过模型对象,分派器来展示请求结果视图。其中核心类是DispatcherServlet,它是一个Servlet,顶层是实现的Servlet接口。

(1)客户端(浏览器)发送请求,直接请求到DispatcherServlet。

(2)DispatcherServlet根据请求信息调用HandlerMapping,解析请求对应的Handler。

(3)解析到对应的Handler后,开始由HandlerAdapter适配器处理。

(4)HandlerAdapter会根据Handler来调用处理器开处理请求,并处理相应的业务逻辑。

(5)处理器处理完业务后,会返回一个ModelAndView对象,Model是返回的数据对象,View是个逻辑上的View。

(6)ViewResolver会根据逻辑View查找实际的View。

(7)DispatercherServlet把返回的Model传给View。

(8)将View返回给请求者(浏览器)

设计模式-7大原则

1.开闭原则:一个软件实体应当对拓展开放,对修改关闭。即:软件实体应尽量在不修改原有代码的情况下进行拓展。
2.里氏代换原则:子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
3.依赖倒转原则:抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而非针对实现编程。
4.单一职责原则:一个类只负责一个功能领域中相应的职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。
5.接口隔离原则:使用多个专门的接口,而不使用单一的总接口。即 客户端不应该依赖于那些它不需要的接口。
6.迪米特法则:一个软件实体应当尽可能少地与其他实体发生作用。(无熟人难办事),降低耦合
7合成复用原则 尽量使用组合/聚合的方式,而不是使用继承。

mysql锁

读锁(共享锁):Shared Locks(S锁),针对同一份数据,多个读操作可以同时进行而不会互相影响
写锁(排它锁):Exclusive Locks(X锁),当前写操作没有完成前,它会阻断其他写锁和读锁
IS锁:意向共享锁、Intention Shared Lock。当事务准备在某条记录上加S锁时,需要先在表级别加一个IS锁。
IX锁:意向排他锁、Intention Exclusive Lock。当事务准备在某条记录上加X锁时,需要先在表级别加一个IX锁。
IS、IX锁是表级锁,它们的提出仅仅为了在之后加表级别的S锁和X锁时可以快速判断表中的记录是否被上锁,以避免用遍历的方式来查看表中有没有上锁的记录。就是说当对一个行加锁之后,如果有打算给行所在的表加一个表锁,必须先看看该表的行有没有被加锁,否则就会出现冲突。IS锁和IX锁就避免了判断表中行有没有加锁时对每一行的遍历。直接查看表有没有意向锁就可以知道表中有没有行锁。
InnoDB行锁是通过给索引上的索引项加锁来实现的。只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁。
MySQL的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键,是会出现锁冲突的。

快照读/当前读
当前读:读取记录数据的最新版本(有锁)
insert/update/delete / select…for update / select…in share mode
快照读:读取记录数据的可见版本(最新版本/历史版本 无锁)
select column/* from table

对于INSERT、UPDATE和DELETE,InnoDB 会自动给涉及的数据加排他锁;对于一般的SELECT语句,InnoDB 不会加任何锁,事务可以通过以下语句显式加共享锁或排他锁。
共享锁:SELECT … LOCK IN SHARE MODE; 当前读

排他锁:SELECT … FOR UPDATE;当前读
如果等值条件 值不存在的情况下加的是间隙锁,或者范围查询,加的也是间隙锁
insert 会对插入成功的行加上排他锁。
索引选择性
1、索引的选择性是指索引列中不同值的数目和表的记录数的比值。假如表里面有1000条数据,表索引列有980个不同的值,这时候索引的选择性就是980/1000=0.98 。索引的选择性越接近1,这个索引的效率很高。

2、性别可以认为是3种,男,女,其他。如果创建索引,查询语句 性别=‘男’的数据,索引的选择性就是3/1000=0.003。索引的选择性值很低,对查询提升不大,所以性别建索引意义不大。

当命中多个索引时,走选择性好的索引。

浅拷贝和深拷贝

浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。
深拷贝:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。

访问修饰符

在这里插入图片描述

jvm线程同步

1.互斥同步:synchronized、lock(悲观锁策略)
2.非阻塞同步:如果有线程竞争,不断重试,直到成功为止。(乐观锁策略)。CAS 原子类
3.无同步方案:
(1)可重入代码:代码可重入的判断原则:如果一个方法,输入相同的数据,总是返回相同的结果。
(2)线程本地存储 (ThreadLocal类)
共享数据的代码在同一个线程中执行,将共享数据的可见范围限制在同一线程内

异常和错误

Exception:

1.可以是可被控制(checked)或者不可控制(unchecked);

2.表示一个由程序员导致的错误;

3.应该在应用程序级被处理;

Error:

1.总是不可控制的(unchecked);

2.经常用来表示系统错误或者底层资源错误;

3.如果可能的话,应该在系统级被捕捉;
如内存泄露

beanfactory和FactoryBean

BeanFactory是个Factory,也就是IOC容器或对象工厂,FactoryBean是个Bean。在Spring中,所有的Bean都是由BeanFactory(也就是IOC容器)来进行管理的。但对FactoryBean而言,这个Bean不是简单的Bean,而是一个能生产或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器模式类似。使用传统方式配置下面对象的时,Car的每个属性分别对应一个元素标签。而用FactoryBean的方式实现就灵活点。

(tcp复用)IO复用模型:select、poll、epoll

select:无差别轮询所有流,找出能读出数据,或者写入数据的流,对他们进行操作。所以select具有O(n)的无差别轮询复杂度,同时处理的流越多,无差别轮询时间就越长。
poll:本质上和selector没有区别,但是它没有最大连接数的限制,它是基于链表来存储的.
epoll:活跃的socket才会主动调用callback,会把哪个流发生了怎样的I/O事件通知我们。所以我们说epoll实际上是事件驱动(每个事件关联上fd)的,此时我们对这些流的操作都是有意义的。(复杂度降低到了O(1))。
表面上看epoll的性能最好,但是在连接数少并且连接都十分活跃的情况下,select和poll的性能可能比epoll好,毕竟epoll的通知机制需要很多函数回调。

mysql查询优化

1.考虑在 where 及 order by 涉及的列上建立索引,尽量走索引的查询
2.通过explain(查询优化神器)用来查看SQL语句的执行结果,可以帮助选择更好的索引和优化查询语句,写出更好的优化语句。
3.重复查询相同数据,将一些查询频率较高的缓存起来。
4.查询时只取出自己需要的字段

数据库三大范式

第一范式(1NF):列不可再分
第二范式(2NF)属性完全依赖于主键
第三范式(3NF)属性不依赖于其它非主属性 属性直接依赖于主键

如何防止表单重复提交

1.给数据库增加唯一键约束
2.利用Session或者利用redis防止表单重复提交
在 session 中存放一个特殊标志。当表单页面被请求时,生成一个特殊的字符标志串,存在 session 中,同时放在表单的隐藏域里。接受处理表单数据时,检查标识字串是否存在,并立即从 session 中删除它,然后正常处理数据。 如果发现表单提交里没有有效的标志串,这说明表单已经被提交过了,忽略这次提交。

java基础集合接口

在这里插入图片描述

Set:不包含重复元素的Collection。
  List:有顺序的collection,并且可以包含重复元素。
  Map:可以把键(key)映射到值(value)的对象,键不能重复。
  List,Set继承自Collection接口,而Map没有继承自Collection接口。

java线程安全类

set
HashSet -> CopyOnWriteArraySet
TreeSet -> ConcurrentSkipListSet
list
vector
queue
LinkedBlockingQueue
ConcurrentLinkedQueue
dequeue
LinkedBlockingDeQueue
ConcurrentLinkedDeQueue

多线程遇到的问题

1.操作原子性
2.数据的可见性
3.产生死锁、活锁、饥饿
4.线程创建和线程上下文切换的开销产生性能问题

活锁:桥两头开来两辆车A和B,A比较礼貌,示意B先过,B也比较礼貌,示意A先过,结果两人一直谦让谁也过不去
饥饿:高优先级的线程一直在运行,消耗cpu资源,所有的低优先级线程一直处于等待f

redis宕机处理

如果只有一台redis,肯定会造成数据丢失,无法挽救
多台redis或者是redis集群 ,宕机则需要分为在主从模式下区分来看:
slave从redis宕机
配置主从复制的时候才配置从的redis,从的会从主的redis中读取主的redis的操作日志,求达到主从复制。
1)在Redis中从库重新启动后会自动加入到主从架构中,自动完成同步数据;
2)如果从数据库实现了持久化,可以直接连接到主的上面,只要实现增量备份(宕机到重新连接过程中,主的数据库发生数据操作,复制到从数据库),重新连接到主从架构中会实现增量同步。
Master 宕机
假如主从都没数据持久化,此时千万不要立马重启服务,否则可能会造成数据丢失,正确的操作如下:
1.在slave数据上执行SLAVEOF ON ONE,来断开主从关系并把slave升级为主库
2.此时重新启动主数据库,执行SLAVEOF,把它设置为从库,连接到主的redis上面做主从复制,自动备份数据。以上过程很容易配置错误,可以使用redis提供的哨兵机制来简化上面的操作。简单的方法:redis的哨兵(sentinel)的功能

分布式session

1.粘性session
粘性Session是指将用户锁定到某一个服务器上,比如上面说的例子,用户第一次请求时,负载均衡器将用户的请求转发到了A服务器上,如果负载均衡器设置了粘性Session的话,那么用户以后的每次请求都会转发到A服务器上,相当于把用户和A服务器粘到了一块,这就是粘性Session机制。
以Nginx为例,在upstream模块配置ip_hash属性即可实现粘性Session。
2.服务器session复制
任何一个服务器上的session发生改变(增删改),该节点会把这个 session的所有内容序列化,然后广播给所有其它节点,不管其他服务器需不需要session,以此来保证Session同步
搭建tomcat集群修改server.xml配置 会以广播的形式复制
3.共享session服务器
将用户的session状态从服务中剥离出来,放到一个独立的session服务器上,集群中有关session的服务都来访问该服务器。可以使用redis来建session服务器。

JDK动态代理

1.创建一个实现接口InvocationHandler的类,它必须实现invoke方法
2.创建被代理的类以及接口
3.通过Proxy的静态方法
newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h)创建一个代理
4.通过代理调用方法

jvm

判别对象是否已死

可以通过引用计数法(*reference counting),为每个对象添加一个引用计数器,用来统计指向该对象的引用个数。 一旦某个对象的引用计数器为0,则说明该对象已经死亡,便可以被回收了。

目前Java虚拟机的主流垃圾回收器采取的是可达性分析算法
这个算法的实质在于将一系列GC Roots作为初始的存活对象合集(live set),然后从该合集出发,探索所有能够被该合集引用到的对象,并将其加入到该和集中,这个过程称之为标记(mark)。 最终,未被探索到的对象便是死亡的,是可以回收的。

GC roots对象选择

1.虚拟机栈(栈帧中的本地变量表)中引用的对象
2.方法区中类静态属性引用的对象
3.方法区中常量引用的对象
4.本地方法栈中JNI(即一般说的native方法)中引用的对象

Gc root 查询

有两种查找GC Roots的方法:

一种是遍历方法区和栈区来查找(保守式GC);

一种是通过OopMap的数据结构来记录引用的位置(准确式GC),如在类加载过程中,JIT编译过程中,分别记录下 类成员 和 调用栈 中的引用的调用信息。对应OopMap的位置即可作用一个安全点。线程只有到达安全点时才能暂停下来进行可达性分析。

==和equals的区别是什么?

= = 等于比较运算符,如果进行比较的两个操作数都是基本类型,他们的值相等,也都将返回true.如果两个操作数都是引用类型,那么只有当两个引用变量的类型具有父子关系时才可以比较,而且这两个引用必须指向同一个对象,才会返回true.(在这里我们可以理解成= =比较的是两个变量的内存地址)
而equals()是Object提供的一个方法.Object中equals()方法的默认实现就是返回两个对象==的比较结果.但是equals()可以被重写,所以我们在具体使用的时候需要关注equals()方法有没有被重写.

事务

是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作;这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行;事务是一组不可再分割的操作集合(工作逻辑单元);

innodb实现acid

1.原子性
undo log日志
undo log 属于逻辑日志 , 储存的是 sql 的逻辑, 当回滚的时候 undo log 会执行相反的的逻辑, 比如 insert 就会 delete , delete 就会 insert, update 了就会 update 回去
2.持久性
redo log日志
数据修改的时候, 先写入 redo log ,再写入数据库, 如果还没写入数据库就发生数据库宕机, 重启之后, 会自动读取 redo log 的数据对数据库进行恢复。
3.隔离性
通过锁、多版本并发控制MVCC解决
4.一致性
依赖其他原则, 数据库本身(比如 string 类型和 int 类型不能混合使用, varchar 有长度限制)和应用层共同努力

java内存模型

Java内存模式是一种虚拟机规范。规定了一个线程如何和何时可以看到由其他线程修改过后的共享变量的值,以及在必须时如何同步的访问共享变量。
JMM定义了一些语法集,这些语法集映射到Java语言中就是volatile、synchronized等关键字。

cpu调度算法

先到先服务调度算法
最短任务优先的调度算法
优先级调度算法
轮询调度算法

页面缺页调度

先入先出页面置换算法
最佳置换算法
最近最少使用算法
最少使用置换算法

http协议与tcp协议区别

1.http是一个简单的请求-响应协议。TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议。
2.http是超文本传输协议,是应用层的协议,以TCP为基础
tcp是传输控制协议,是传输层的协议,以IP协议为基础
3.Http协议是建立在TCP协议基础之上的。当浏览器需要从服务器 获取网页数据的时候,会发出一次http请求。Http通过TCP建立起一个到服务器的通道。
4.Http是无转态的连接,TCP是有状态的连接、
有状态服务,服务会存储请求上下文相关的数据信息,先后的请求是可以有关联的。
无状态服务不会记录服务状态,不同请求之间也是没有任何关系。

osi tcp/ip 5层协议

osi:应用层、表示层、会话层、运输层、网络层、数据链路层、物理层
tcp/ip:应用层、运输层、网际层、链路层
五层协议:应用层、运输层、网络层、数据链路层、物理层

抽象类和接口

接口和抽象类的相同点
1、抽象类和接口都不能被直接实例化,他们都需要被继承或实现。(类和抽象类中间是继承关系,类和接口是实现关系,接口和接口之间可以有继承关系)
2、接口和抽象类都可以包含抽象方法,实现或继承他们的普通类必须重写抽象方法。
3、抽象类和接口,都可以体现在多态中,作为变量数据类型接收子类实例对象。

接口和抽象类的不同点
1、用途不同:
抽象类中抽取了多个子类的共性,作为模版存在,用于被子类继承实现代码复用。
接口定义了某种功能的规范,当类需要新增其功能时,只需要实现接口,重写接口的规范(抽象方法)即可,同时接口也弥补了java单继承的缺陷。
抽象类
2、接口里一般包含抽象方法(default的普通方法),抽象类则包含普通方法和抽象方法。
3、接口不能定义静态方法,抽象类可以(jdk1.8之后可以)。
4、接口中的成员变量是静态常量,
使用public static final修饰。抽象类则可以定义静态常量,也可以定义非静态成员变量。
5.抽象类 和 接口 都是用来抽象具体对象的. 但是接口的抽象级别最高
6.抽象类主要用来抽象类别,接口主要用来抽象功能.

枚举类

在这里插入图片描述

concurrentHashMap不支持null原因

ConcurrentHashMap不能put null 是因为 无法分辨是key没找到的null还是有key值为null,这在多线程里面是模糊不清的,所以压根就不让put null。所以当为null会直接抛出异常。

ConcurrentHashmap和Hashtable都是支持并发的,这样会有一个问题,当你通过get(k)获取对应的value时,如果获取到的是null时,你无法判断,它是put(k,v)的时候value为null,还是这个key从来没有做过映射。HashMap是非并发的,可以通过contains(key)来做这个判断。而支持并发的Map在调用m.contains(key)和m.get(key),m可能已经不同了。

@springbootapplication组合注解

第一个: @SpringBootConfiguration( 声明当前类是一个配置类, 简化xml操作 )
第二个:@EnableAutoConfiguration( 开启自动配置, 告诉SpringBoot基于所添加的依赖 )
第三个:@ComponentScan( 配置组件扫描的指令 )

map和set去重

set的去重是通过两个函数__hash__和__eq__结合实现的。
1、当两个变量的哈希值不相同时,就认为这两个变量是不同的
2、当两个变量哈希值一样时,调用__eq__方法,当返回值为True时认为这两个变量是同一个,应该去除一个。返回FALSE时,不去重

set底层

我们创建一个Set集合具体实现类在底层其实就是创建了一个具体的Map集合的实现类对象
也就是我们的Set底层其实是通过Map进行存储的
比如我们的HashSet底层其实就是创建了一个HashMap,也就是我们的HashSet其实就是通过HashMap进行存储的
HashSet是存储单列数据,也就是一个一个的数据
HashMap是存储双列数据,也就是一个键值对一个键值对进行存储(键值对: key — value)
其实HashSet中添加元素其实是添加到了HashMap中的key中了
set的add方法,
在这里插入图片描述
在这里插入图片描述
为什么这个要让Object作为Value存入呢
HashSet底层其实就是维护着HashMap,HashSet的add方法,其实是将值,作为HashMap的key存入。

而如果HashSet的value 用null值的话,那么在删除操作的时候,可能remove方法返回的值就永远都是true了。在这里插入图片描述

try catch finally执行流程

catch可省略
在这里插入图片描述

守护线程

Java的线程分为两种:User Thread(用户线程)、DaemonThread(守护线程)。

守护线程是程序运行的时候在后台提供一种通用服务的线程。只要当前JVM实例中尚存任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束是,守护线程随着JVM一同结束工作,Daemon作用是为其他线程提供便利服务,守护线程最典型的应用就是GC(垃圾回收器),他就是一个很称职的守护者。

加密算法

对称加密算法 :常用的算法包括DES、3DES、AES、DESX、Blowfish、RC4、RC5、RC6。推荐用AES。
非对称加密算法:常见的非对称加密算法:RSA、DSA(数字签名用)、ECC(移动设备用)、Diffie-Hellman、El Gamal。推荐用ECC(椭圆曲线密码编码学散列算法(Hash散列算法(Hash算法—单向加密算法):常见的Hash算法:MD2、MD4、MD5、HAVAL、SHA、SHA-1、HMAC、HMAC-MD5、HMAC-SHA1。推荐MD5、SHA-1。

解决hash冲突

什么是hash冲突
简单来说就是一些值通过hash函数计算之后得到的hash值一样

一、链地址法
在hash值为k的元素对应的地址上建立一个链表,然后将所有hash值为K的元素都放在此链表上。
优点:因为是链表这种数据结构所有增删快
缺点:查询慢
在Java1.7以及之前都采用的是链地址法来解决HashMap中的hash冲突
在java1.8之后当链表长度大于8,且总数据量大于64的时候,链表就会转化成红黑树
二、开放定址法
当通过hash函数H(key)产生一个地址p=H(key)产生了hash冲突时,在p的基础上进行地址的探测得到新地址p2如果p2还产生hash冲突则再以p为基础再次进行探测直到得到不会产生hash冲突的地址。
这种方法拥有一个通用的再散列函数形式:Hi=(H(key)+di)%m i=1,2,3,…n;
H(key)是哈希函数 di是增量 m是哈希表的长度
其中i的取值不同也就是di不同地址的探测方式也不同

三、再哈希法
使用多个hash函数 当一个函数产生冲突的时候再使用下一个函数再此进行Hash求值如果还产生冲突则再使用下一个函数直到不再产生冲突为止。
Hi=RHi(key) i=1,2,3,4,…n

四、建立公共溢出区
将hash表分成两部分基本表和溢出表凡是和基本表中元素产生Hash冲突的元素一律放入溢出表

负载均衡算法

1)随机算法:负载均衡方法随机的把负载分配到各个可用的服务器上,通过随机数生成算法选取一个服务器,然后把连接发送给它。虽然许多均衡产品都支持该算法,但是它的有效性一直受到质疑,除非把服务器的可运行时间看的很重。

2)轮询算法:轮询算法按顺序把每个新的连接请求分配给下一个服务器,最终把所有请求平分给所有的服务器。轮询算法在大多数情况下都工作的不错,但是如果负载均衡的设备在处理速度、连接速度和内存等方面不是完全均等,那么效果会更好。

3)加权轮询算法:该算法中,每个机器接受的连接数量是按权重比例分配的。这是对普通轮询算法的改进,比如你可以设定:第三台机器的处理能力是第一台机器的两倍,那么负载均衡器会把两倍的连接数量分配给第3台机器。

4)动态轮询算法:类似于加权轮询,但是,权重值基于对各个服务器的持续监控,并且不断更新。这是一个动态负载均衡算法,基于服务器的实时性能分析分配连接,比如每个节点的当前连接数或者节点的最快响应时间等。

5)最快算法:最快算法基于所有服务器中的最快响应时间分配连接。该算法在服务器跨不同网络的环境中特别有用。

6)最少连接算法:系统把新连接分配给当前连接数目最少的服务器。该算法在各个服务器运算能力基本相似的环境中非常有效。

linux查看网络状态和内存占用

netstat -an 查看所有连接。
-a:列出所有网络状态,包括 Socket 程序;
-c秒数:指定每隔几秒刷新一次网络状态;
-n:使用 IP 地址和端口号显示,不使用域名与服务名;
-p:显示 PID 和程序名;
-t:显示使用 TCP 协议端口的连接状况;
-u:显示使用 UDP 协议端口的连接状况;
-I:仅显示监听状态的连接;
-r:显示路由表;
top 查看进程占用内存
free查看系统内存

synchronize的优化

1.锁膨胀
synchronized 是重量级锁,也就是说 synchronized 在释放和获取锁时都会从用户态转换成内核态,而转换的效率是比较低的。但有了锁膨胀机制之后,synchronized 的状态就多了无锁、偏向锁以及轻量级锁了,这时候在进行并发操作时,大部分的场景都不需要用户态到内核态的转换了,这样就大幅的提升了 synchronized 的性能。
2.自适应自旋锁
自旋锁是指通过自身循环,尝试获取锁的一种方式
自旋锁优点在于它避免一些线程的挂起和恢复操作,因为挂起线程和恢复线程都需要从用户态转入内核态,这个过程是比较慢的,所以通过自旋的方式可以一定程度上避免线程挂起和恢复所造成的性能开销。
3.锁的粗化
如果在 for 循环中定义锁,那么锁的范围很小,但每次 for 循环都需要进行加锁和释放锁的操作,性能是很低的;但如果我们直接在 for 循环的外层加一把锁,那么对于同一个对象操作这段代码的性能就会提高很多
锁粗化的作用:如果检测到同一个对象执行了连续的加锁和解锁的操作,则会将这一系列操作合并成一个更大的锁,从而提升程序的执行效率。
4.锁消除
JVM 虚拟机如果检测不到某段代码被共享和竞争的可能性,就会将这段代码所属的同步锁消除掉,从而到底提高程序性能的目的。

kill和kill -9

kill信号只是通知对应的进程要进行"安全、干净的退出",程序接到信号之后,退出前一般会进行一些"准备工作",如资源释放、临时文件清理等等,如果准备工作做完了,再进行程序的终止。但是,如果在"准备工作"进行过程中,遇到阻塞或者其他问题导致无法成功,那么应用程序可以选择忽略该终止信号。
kill -9就相对强硬一点,系统会发出SIGKILL信号,他要求接收到该信号的程序应该立即结束运行,不能被阻塞或者忽略。

排序算法

冒泡排序

首先从数组的第一个元素开始到数组最后一个元素为止,对数组中相邻的两个元素进行比较,如果位于数组左端的元素大于数组右端的元素,则交换这两个元素在数组中的位置。
算法的时间复杂度为最好O(n),最坏O(n2),平均(n2),是稳定的,空间复杂为O(1)

选择排序

每一趟在n-i+1(i=1,2,…,n-1)个记录中选取关键字最小的记录作为有序序列中第i个记录。算法的时间复杂度为O(n^2)。
算法的时间复杂度为最好O(n²),最坏O(n2),平均(n2),是不稳定的,空间复杂为O(1)
举个例子,序列arr = [5 8 5 2 9],我们知道第一遍选择第1个元素5会和2交换,那么原序列中两个5的相对前后顺序就被破坏了,所以选择排序是一个不稳定的排序算法。

插入排序

将无序序列插入到有序序列中,将数组第一个数作为有序序列,其他为无序序列,依次将无序序列的数插入到有序序列。算法的时间复杂度为O(n^2)。
算法的时间复杂度为最好O(n),最坏O(n2),平均(n2),是稳定的,空间复杂为O(1)

希尔排序

先将待排记录序列分割成为若干子序列分别进行插入排序,待整个序列中的记录"基本有序"时,再对全体记录进行一次直接插入排序。
算法的时间复杂度为最好O(n1.3),最坏O(n2),平均(n1.3),是不稳定的,空间复杂为O(1)

快速排序

从待排序列中任意选取一个记录(通常选取第一个记录)作为基准值,然后将记录中关键字比它小的记录都安置在它的位置之前,将记录中关键字比它大的记录都安置在它的位置之后。
算法的时间复杂度为最好O(nlogn),最坏O(n2),平均(nlogn),是不稳定的,空间复杂为O(logn)

归并排序

初始序列含有n个记录,则可以看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到(表示不小于x的最小整数)个长度为2(或者是1)的有序子序列,再两两归并。如此重复,直到得到一个长度为n的有序序列为止。
算法的时间复杂度为最好O(NlogN),最坏O(NlogN),平均(nlogn),是稳定的,空间复杂为O(n)

堆排序

可以将堆看做是一个完全二叉树。并且,每个结点的值都大于等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于等于其左右孩子结点的值,称为小顶堆。
将待排序列构造成一个大顶堆(或小顶堆),整个序列的最大值(或最小值)就是堆顶的根结点,将根节点的值和堆数组的末尾元素交换,此时末尾元素就是最大值(或最小值),然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素中的次大值(或次小值),如此反复执行,最终得到一个有序序列。
算法的时间复杂度为最好O(NlogN),最坏O(NlogN),平均(nlogn),是不稳定的,空间复杂为O(1)
如何建堆
从第一个非叶子结点开始向下交换(即每次调整都是从父节点、左孩子节点、右孩子节点三者中选择最大者跟父节点进行交换(交换之后可能造成被交换的孩子节点不满足堆的性质,因此每次交换之后要重新对被交换的孩子节点进行调整)。有了初始堆之后就可以进行排序了。
如何排序
排序过程为取出堆顶元素的操作,实现细节:取出堆顶元素与堆尾元素交换,将堆顶元素向下调整堆———在每一个调整过程中当不向下调整时即为调整结束。
选择快排而不选择堆排序原因
1、堆排序的数据交换次数要高于快速排序:在堆排序(小根堆)的时候,每次总是将最小的元素移除,然后将最后的元素放到堆顶,再让其自我调整。这样一来,有很多比较将是被浪费的,因为被拿到堆顶的那个元素几乎肯定是很大的,而靠近堆顶的元素又几乎肯定是很小的,最后一个元素能留在堆顶的可能性微乎其微,最后一个元素很有可能最终再被移动到底部。在堆排序里面有大量这种近乎无效的比较。随着数据规模的增长,比较的开销最差情况应该在(线性*对数)级别,如果数据量是原来的10倍,那么用于比较的时间开销可能是原来的10log10倍。
2、堆排序访问数据的方式没有快速排序友好:堆排序的过程中,需要有效的随机存取。比较父节点和子节点的值大小的时候,虽然计算下标会很快完成,但是在大规模的数据中对数组指针寻址也需要一定的时间。而快速排序只需要将数组指针移动到相邻的区域即可。在堆排序中,会大量的随机存取数据;而在快速排序中,只会大量的顺序存取数据。随着数据规模的扩大,这方面的差距会明显增大。在这方面的时间开销来说,快速排序只会线性增长,而堆排序增加幅度很大,会远远大于线性。
大数据用归并排序
在大数据场景中,数据量远大于计算机的内存,磁盘和网络的IO又是无法解决的瓶颈,就需要用到分治思想。
步骤:
1.计算机上将1000G的数据每次加载5G到内存中做排序(这一步不一定要用归并排序,可以用快排等其他排序方式)输出一个5G的小文件,这样得到200份内部有序外部无序的5G文件。
2. 从每个5G文件中读取25M数据到内存中,内存中有200个有序的数组。这个就要用到“多路归并”,创建一个有序队列,从200个数组每个读取一个元素放入有序队列。
稳定的排序
归并排序 冒泡排序 插入排序

BIO NIO AIO

BIO即Blocking I/O(阻塞 I/O)
数据的读取写入必须阻塞在一个线程内等待其完成再进行后续操作。
NIO即Non-Blocking I/O(非阻塞 I/O)
单线程通过selector 从channel读取数据到buffer,同时可以继续做别的事情,当数据读取到buffer中后,线程再继续处理数据。写数据也是一样的。
AIO即Asynchronous I/O(异步 I/O)
异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。
AIO属于异步模型, 用户线程可以同时处理别的事情,我们怎么进一步加工处理结果呢? Java在这个模型中提供了两种方法:
一种是基于”回调”,我们可以实现CompletionHandler接口,在调用时把回调函数传递给对应的API即可

另一种是返回一个Future。处理完别的事情,可以通过isDone() 可查看是否已经准备好数据,通过get()方法等待返回数据。

内存泄漏和内存溢出

一:内存溢出:程序在申请内存时,没有足够的内存空间供其使用,比如Interger申请到的内存空间,却要存long才能放下的数据

二:内存泄漏:指程序申请到内存空间后,无法释放已经申请的内存空间,积累多了,再多的内存都会被占进

mongodb选择b树

作为非关系型的数据库,MongoDB 对于遍历数据的需求没有关系型数据库那么强,它追求的是读写单个记录的性能;由于 B 树的非叶结点也可以存储数据,所以查询一条数据所需要的平均随机 IO 次数会比 B+ 树少,使用 B 树的 MongoDB 在类似场景中的查询速度就会比 MySQL 快。

网络攻击

XSS攻击、SQL注入、CSRF攻击、上传文件攻击、DDos攻击
xss:跨站脚本攻击,它指的是恶意攻击者往Web页面里插入恶意的html+javascript的脚本和代码,当用户浏览该页之时,嵌入其中Web里面的恶意脚本会被执行,从而达到恶意用户的特殊目的。
sql注入攻击:SQL注入攻击通过构建特殊的输入作为参数传入Web应用程序,而这些输入大都是SQL语法里的一些组合,通过执行SQL语句进而执行攻击者所要的操作,比如删库等。
上传文件攻击:如果我们的网站允许别人上传文件,那么文件可能是可执行的脚本,可能是病毒或者木马文件,如果是脚本的话,可能会在服务器执行,比如黑掉你的服务器、连接你的数据库之类的。
DDoS:distributed denial of service分布式拒绝服务攻击
DoS攻击,简单来说就是黑客发送大量的请求,导致你的服务器线程资源全部打满,正常用户根本无法发送请求,你的网站就宕机了。DoS攻击是一对一的,就是黑客搞一台高性能服务器,拼命发送请求给你的一台服务器。
DDoS的意思就是黑客控制大量的机器,比如普通人的电脑,或者是一些公司的服务器,被他的一些木马植入给控制了,就是所谓的“肉鸡”,然后黑客下达指令,让所有肉鸡一起发送请求给攻击目标,直接搞瘫你的服务器。

背包问题

通过 动态规划&回溯&分支限界 解决
贪心算法不能解决该问题

内存泄漏原因

1、大量使用静态变量
2、连接资源未关闭
3.ThreadLocal的错误使用
4.finalize方法(这个对象含有finalize,进入了队列但一直没有被调用的这段时间,会一直占用内存。造成内存泄漏。)
5.equals()和hashCode()方法使用不当

http组成部分

请求报文
请求行 — 包含http方法,页面地址,http协议,http版本;
请求头 — 包含一些key:value的值,eg: host、Cache-Control,Accept,Cookie等;
空行 — 用来告诉服务端往下就是请求体的部分啦;
请求体 — 就是正常的query/body参数。
响应报文
状态行 — 包含http方法,http协议,http版本,状态码;
响应头 — 包含一些key:value的值,eg: Content-type,Set-cookie, Cache-Control, Date, Server等;
空行 — 用来告诉客户端往下就是响应体的部分;
响应体 — 就是服务端返回的数据。

java元注解

@Target
当一个注解被 @Target 注解时,指定使用的范围,开发中将注解用在类上(如@Controller)、字段上(如@Autowired)、方法上(如@RequestMapping)、方法的参数上(如@RequestParam)等比较常见

@Retention 存活时间
注解的生命周期,用于定义注解的存活阶段,可以存活在源码级别、编译级别(字节码级别)、运行时级别

@Documented
它的作用是能够将注解中的元素包含到 Javadoc 中去

@Inherited 注解继承
允许子类继承父类中的注解,可以通过反射获取到父类的注解

@Repeatable
@Repeatable注解表明标记的注解可以多次应用于相同的声明或类型,此注解由Java SE 8版本引入。

大数据量查询

1.分库分表
方案描述:数据按一定规则进行拆分存储。
2.预查询 + 缓存
方案描述:针对非实时统计接口使用预查询结合缓存,提前将交易数据进行统计查询并将结果放入缓存,同时采用定时任务定时去刷新缓存数据。
3.查询走缓存,走覆盖索引。

mysql 强制走索引

在这里插入图片描述

mybatis传参

方法1:顺序传参法(不推荐)
在这里插入图片描述
方法2:@Param注解传参法(推荐)
在这里插入图片描述
方法3:Map传参法(推荐)
在这里插入图片描述
方法4:Java Bean传参法(推荐)在这里插入图片描述

循环依赖解决

我们通过属性注入的方法,使得类A和类B不在出现例子1中的错误,并且使得两个相互依赖的类A和B可以调用相互依赖的方法。
Spring通过三级缓存解决了循环依赖。
例子1:
在这里插入图片描述
解决:
在这里插入图片描述在这里插入图片描述

同步通信与异步通信区别

1、同步通信要求接收端时钟频率和发送端时钟频率一致,发送端发送连续的比特流;异步通信时不要求接收端时钟和发送端时钟同步,发送端发送完一个字节后,可经过任意长的时间间隔再发送下一个字节。

2、同步通信效率高,异步通信效率较低。

3、同步通信较复杂,双方时钟的允许误差较小;异步通信简单,双方时钟可允许一定误差。

4、同步通信可用于点对多点,异步通信只适用于点对点。

forward和redirect

1.直接转发方式(forward):客户端和浏览器只发出一次请求,Servlet、HTML、JSP或其它信息资源,由第二个信息资源响应该请求,在请求对象request中,保存的对象对于每个信息资源是共享的。

间接转发方式(redirect)实际是两次HTTP请求,服务器端在响应第一次请求的时候,让浏览器再向另外一个URL发出请求,从而达到转发的目的。

2.redirect:重新开始一个request,原页面的request生命周期结束。

forward:forward另一个连接的时候。request变量是在其生命周期内的。另一个页面也可以使用,其实质是把目标地址include。

3.redirect在浏览器中显示的是被请求的URL,forward在浏览器中不显示被请求的URL

java中final修饰类、方法、变量

(1)修饰类:表示类不可被继承

(2)修饰方法:表示方法不可被覆盖

(3)修饰变量:表示变量一旦被赋值就不可以更改它的值。java中规定final修饰成员变量必须由程序员显示指定变量的值。
final修饰的引用一旦指向一个对象,就不能在重新指向其他对象,虽然指向不能改变,但是该引用指向的对象内部的数据是可以修改的;

java静态代码块的执行

1、调用类静态变量会执行静态代码块

2、调用静态方法时静态代码块会执行

3、创建对象时,静态代码块会执行

static修饰类

如果一个类要被声明为static的,只有一种情况,就是静态内部类。

1.静态内部类跟静态方法一样,只能访问静态的成员变量和方法,不能访问非静态的方法和属性,但是普通内部类可以访问任意外部类的成员变量和方法
2.静态内部类可以声明普通成员变量和方法,而普通内部类不能声明static成员变量和方法。
3.静态内部类可以单独初始化:
Inner i = new Outer.Inner();
普通内部类初始化:
Outer o = new Outer();
Inner i = o.new Inner();

静态内部类使用场景一般是当外部类需要使用内部类,而内部类无需外部类资源,并且内部类可以单独创建的时候会考虑采用静态内部类的设计。
java中的内部类和接口加在一起,可以解决常被C++程序员抱怨java中存在的一个问题:没有多继承。实际上,C++的多继承设计起来很复杂,而java通过内部类加上接口,可以很好的实现多继承的效果。
Java 只能继承一个类,因为Java是面向对象语言,一个类可继承的属性不应该来自多个类
所以Java的思想就不支持多重继承,但是支持对象的扩展,也就是接口

threadlocal用static修饰

我们知道,一个ThreadLocal实例对应当前线程中的一个TSO实例。因此,如果把ThreadLocal声明为某个类的实例变量(而不是静态变量),那么每创建一个该类的实例就会导致一个新的TSO实例被创建。显然,这些被创建的TSO实例是同一个类的实例。于是,同一个线程可能会访问到同一个TSO(指类)的不同实例,这即便不会导致错误,也会导致浪费(重复创建等同的对象)!因此,一般我们将ThreadLocal使用static修饰即可。

mybatis的批量操作

批量保存用户,并返回每个用户插入的ID
在这里插入图片描述

在这里插入图片描述

sql引用
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值