Java面试题,看完这一篇就够了,送给正在找工作的你(持续更新)

--------------------------------------基础篇------------------------------------------

1.集合为什么要设计出一个迭代器

首先使用迭代器适用性强,因为如果用for循环遍历,需要事先知道集合的数据结构,而且当换了一种集合的话代码不可重用要修改,不符合开闭原则。而Iterator是用同一种逻辑来遍历集合。其次使用Iterator可以在不了解集合内部数据结构的情况下直接遍历,这样可以使得集合内部的的数据不暴露。
for循环的遍历
ArrayList list = new ArrayList<>();
for(int i = 0; i < list.size(); i++){
   System.out.println(list.get(i));
}
迭代器遍历
Iterator it =list.iterator();
while(it.hasNext()){
   System.out.println(it.next());
}

Iterator 和 ListIterator 有什么区别?

  • Iterator 可以遍历 Set 和 List 集合,而 ListIterator 只能遍历 List。
  • Iterator 只能单向遍历,而 ListIterator 可以双向遍历(向前/后遍历)。
  • ListIterator 实现 Iterator 接口,然后添加了一些额外的功能,比如添加一个元素、替换一个元
    素、获取前面或后面元素的索引位置。

2.JDK,JRE,JVM的区别和联系

JDK: Java development kit java开发工具
JRE:Java runtime environment java运行时环境
JVM: Java virtual Machine java虚拟机
在这里插入图片描述

3.==和equals区别

最大的区别一个方法一个使运算符
==比较的时栈中的值。基本数据类型是变量值,引用类型是堆中内存对象的地址
==:如果比较的对象是基本数据类型,则比较的是数值是否相等;如果比较的是引用数据类型,则比较的是对象的地址值是否相等。
equals():用来比较方法两个对象的内容是否相等。

4.简述final的作用,为什么局部内部类和匿名内部类只能访问final变量?

final:用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,被其修饰的类不可继承。

5.String,StringBuffer,StringBuilder的区别及使用场景

String 和 StringBuffer、StringBuilder 的区别在于:

  • String 声明的是不可变的对象,每次操作都会生成新的 String 对象,然后将指针指向新的 String 对象,而 StringBuffer、StringBuilder 可以在原有对象的基础上进行操作,所以在经常改变字符串内容的情况下最好不要使用 String。
  • StringBuffer 和 StringBuilder 最大的区别在于,StringBuffer 是线程安全的,而 StringBuilder 是
    非线程安全的,但 StringBuilder 的性能却高于 StringBuffer,所以在单线程环境下推荐使用
    StringBuilder,多线程环境下推荐使用 StringBuffer。

6.重载和重写

重载:发生在同一个类中,方法名必须相同,参数类型不同,参数个数不同,顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。
重写:发生在父子类,方法名,参数列表必须相同,返回值返回要小于等于父类,抛出的异常范围小于等于父类,访问修饰符大于等于父类,如果父类访问修饰符为private则不能重写该方法。

7.接口和抽象类的区别

1.抽象类可以存在普通成员函数,而接口只能存在public,abstract方法。
2.抽象类的成员变量可以是各种类型的,而接口的成员变量只能是public,abstract,final类型。
3.抽象类只能继承一个,而接口可以实现多个
接口的设计目的,是对类的行为进行约束,因为接口不能规定类不可以有什么行为。它只约束了行为的有无,但不对如何实现行为进行限制。
抽象类的设计目的是代码复用,当不同的类具有某些相同的行为,可以让这些相同的行为派生成一个抽象类。从而达到代码复用的目的
抽象类是对类本质的抽象,表达is a 的关系,什么是什么
接口是对行为的抽象,表达 like a的关系,接口的核心是定义行为,即实现类可以做什么,至于实现类主题是谁,是如何实现的接口不关心。
使用场景: 当关注一个事务的本质的时候,用抽象类,当关注一个操作的时候,用接口。
抽象类的功能要远超于接口,但是定义抽象类的代价高,对于高级语言来说,每个类只能继承一个类,在这个类中,你必须继承或编写出所有子类的所有共性,虽然接口在功能上弱化许多,但是它只针对一个动作的描述,而且你可以在一个类中同时实现多个接口,在设计阶段会降低难度!

8.List和Set的区别

List:有序的,按对象进入的顺序保存元素,可以重复,允许多个null对象,可以使用iterator去除所有元素,再逐一遍历,还可以使用get(int index)获取指定下标的元素
set:无序,不可重复,最多允许一个null元素对象,取元素只能用iterator接口取得所有元素,在逐一遍历各个元素。

9.HashCode与equals

1.如果两个对象相等,则hashcode也一定相同。
2.两个对象相等,对两个对象分别调用equals方法返回true
3.两个对象有相同的hashcode,他们也不一定相等。
4.因此,equals方法被覆盖过,hashcode方法也必须覆盖。
5.hashcode()的默认行为是对堆上的对象产生独特值,如果没有重写hashcode。则该class的两个对象无论如何都不会相等,即使这两个对象指向相同的数据
对象加入hashset时,hashset会先计算对象的hashcode值来判断对象加入的位置,看该位置是否有值,如果没有,hashset会假设对象没有重复出现,但如果发现有值,这时会调用equals方法来检查两个对象是否真的相同,如果相同,hashset就不会让其加入操作成功,如果不同的话,就会重新散列到其他位置,这样大大减少了equals的次数,相应就大大提高了执行速度。

10.ArrayList和LinkedList的区别

ArrayList基于动态数组,连续内存存储,适合下标访问(随机访问),扩容机制:因为数组长度固定,超出长度数据时需要新建数组,然后将老数组的数据拷贝到新数组,如果不是尾部插入数据还会涉及到元素的移动(往后复制一份,插入新元素),使用尾插法并制定初始容量可以极大提升性能,甚至超过LinkedList
LinkedList:基于链表,可以存储在分散的内存中,适合做插入及删除操作,不适合查询:需要逐一遍历,遍历linkedList必须使用iterator不能使用for循环,因为每次for循环体内通过get(i)取得某一元素时都需要对list重新进行遍历,性能消耗极大。
另外不要试图使用indexOf等返回元素索引,并利用其进行遍历,使用indexOf对list进行遍历,当结果为空时会遍历整个列表

11.HashMap和HashTable的区别?底层实现时什么?

区别:
1.HashMap方法没有synchronized修饰,线程不安全,hashtable有,所以线程安全。
2.HashMap允许key和value为null,而hashTable不允许
底层实现:数组+链表实现
jdk8开始链表高度达到8,数组长度超过64,链表转换为红黑树,元素以内部类Node节点存在:
1.计算key的hash值,二次hash然后对数组长度取模,对应到数组下标
2.如果没有产生hash冲突(下标位置没有元素),则直接创建Node存入数组
3.如果产生hash冲突,先进行equal比较,相同则取代该元素,不同,则判断链表高度插入链表,链表高度达到8,并且数组长度达到64转变为红黑树,长度低于6则将红黑树装换回链表
4.key为null,存在下标0的位置
数组扩容

12.ConcurrentHashMap原理,jdk7和jdk8版本的区别

jdk7:
数据结构:ReentranLock+Segment+HashEntry,一个Segment中包含一个HashEntry数组,每个HashEntry又是一个链表结构
元素查询:二次hash。第一次Hash定位到Segment,第二次定位到元素所在的链表的头部!
锁:segment分段锁,segment继承了Reentrantlock,锁定操作的Segment,其他的segment不受影响,并发度为Segment个数,可以通过构造函数指定,数组扩容不会影响其它的segment
get方法无需加锁 volatile保证
jdk8
数据结构:synchorinzed+CAS+Node+红黑树,Node的val和next都用volatile修饰,保证可见性,查找替换,赋值操作都用CAS
锁:锁链表的head节点,不影响其它元素的读写,锁粒度更细,效率更高,扩容时,阻塞所有的读写操作,并发扩容
读操作无锁:
Node的val和next使用volatile修饰,读写线程对该变量互相可见
数组用volatile修饰,保证扩容时被线程感知。

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

java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的机器。这台虚拟的机器在任何平台上都提供给编译程序一个共同的接口
编译程序的时候生产虚拟机能够理解的代码,这种代码叫字节码(.class文件),它不面向任何特定的处理器,只面向虚拟机。
每一种平台的解释器不同,但实现的虚拟机相同,java程序经编译器编译成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码给解释器,解释器再将其编译成特定机器上的机器码,然后在特定的机器上运行,这也解释了java的编译与解释并存的特点
采用字节码好处
java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点,所以java程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,java程序无需重新编译可在多种不同的计算机上运行。

17.java类加载器有哪些?

Jdk自带的三个类加载器,bootstrap ClassLoader,ExtClassLoader,AppClassLoader。
BootStrapClassLoader是ExtClassLoader的父类加载器,默认负责加载%JAVA_HOME%lib下的jar包和class文件
ExtClassLoader是AppClassLoader的父类加载器,负责加载%JAVA_HOME%lib下的jar包和class类。
AppClassLoader是自定义类加载器的父类,负责加载classpath下的类文件,系统类加载器,线程上下文加载器。
继承ClassLoader实现自定义类加载器。

18.双亲委托模型

在这里插入图片描述
双亲委派模型的好处:

  • 主要是为了安全性,避免用户自己编写的类动态替换java的一些核心类,比如String
  • 同时也避免了类的重复加载,因为jvm中区分不同类,不仅仅是根据类名,相同的class文件被不同的classLoader加载就是不同的两个类

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

  • 引用计数法:每个对象有一个引用计数属性,新增一个引用时计数加1,计数为0时可以被回收
  • 可达性分析法:从GC roots可以向下搜索,搜索所走过的路径称为引用练。当一个对象到GC Roots没有任何引用练相连时,则证明此对象是不可用的,那么虚拟机就可以判断是可回收对象。

引用计数法,可能出现A引用了B,B又引用了A,这时候就算他们都不再使用了,但因为相互引用计数器=1 永远无法被回收
GCRoots的对象有

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 方法区中静态类属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(一般说的Native方法)引用的对象
    可达性算法中的不可达对象并不是立即死亡的,对象拥有一次自我拯救的机会,对象被系统宣告死亡至少要经历两次标记过程:第一次是经过可达性分析发现没有与Gc Roots相连接的引用链,第二次是有虚拟机自动建立的Finalizer队列中判断是否需要执行finalize()方法。
    当对象变成(GC roots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收,否则,若对象未执行过finalize方法,将其放入 F-Queue队列,由低优先级线程执行该队列中对象的finalize方法。执行finalize方法完毕后,Gc会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“复活”
    每个对象只能触发一次finalize方法
    由于finalize方法运行代价高昂,不确定性大,无法保证各个对象的调用顺序,不推荐使用。

20.Java中的8种基本数据类型及其取值范围(多看看可能会考)

byte:1字节
short:2字节
int:4个字节
long:8字节
float:4字节
double:8字节
char:2字节
boolean:Java规范中并没有规定boolean类型所占字节数

21说说Java中反射机制?

反射是Java语言被视为动态语言的关键,反射机制允许程序在运行期间借助于Reflection Api取得任何类的内部信息
并能直接操作任意对象的属性和方法。
加载完类后,在堆内存的方法区上就产生了一个class类型的对象,这个对象就包含了完整类的结构信息,可以通过
这个对象看到类的完整结构,这个对象就像一面镜子,通过这面镜子可以看到类的结构所以称为为反射。

22.Java中的Exception和Error有什么区别?

Exception是程序正常运行中预料到可能会出现的错误,并且应该被捕获并进行相应的处理,是一种异常现象
Error是正常情况下不可能发生的错误,Error会导致JVM处于一种不可恢复的状态,不需要捕获处理,比如说OutOfMemoryError

23.Java中的值传递和引用传递可以解释下吗?

  • 值传递,意味着传递了对象的一个副本,即使副本被改变,也不会影响源对象。
  • 引用传递,意味着传递的并不是实际的对象,而是对象的引用。因此,外部对引用对象的改变会反映到所有的对象上。

24.HashMap是线程不安全的是吧?你可以举一个例子吗?

  • HashMap线程不安全主要是考虑到了多线程环境下进行扩容可能会出现HashMap死循环
  • Hashtable线程安全是由于其内部实现在put和remove等方法上使用synchronized进行了同步,所以对单个方法的使用是线程安全的。但是对多个方法进行复合操作时,线程安全性无法保证。 比如一个线程在进行get然后put更新的操作,这就是两个复合操作,在两个操作之间,可能别的线程已经对这个key做了改动,所以,你接下来的put操作可能会不符合预期。

25.HashMap的初始容量,加载因子,扩容增量是多少?

HashMap的初始容量16,加载因子为0.75,扩容增量是原容量的1倍。如果HashMap的容量为16,一次扩容后容量为32。HashMap扩容是指元素个数(包括数组和链表+红黑树中)超过了16*0.75=12之后开始扩容。

26.HashMap的长度为什么是2的幂次方?

  • 我们将一个键值对插入HashMap中,通过将Key的hash值与length-1进行&运算,实现了当前Key的定位,2的幂次方可以减少冲突(碰撞)的次数,提高HashMap查询效率
  • 如果length为2的幂次方,则length-1 转化为二进制必定是11111……的形式,在与h的二进制与操作效率会非常的快,而且空间不浪费
  • 如果length不是2的幂次方,比如length为15,则length-1为14,对应的二进制为1110,在与h与操作,最后一位都为0,而0001,0011,0101,1001,1011,0111,1101这几个位置永远都不能存放元素了,空间浪费相当大,更糟的是这种情况中,数组可以使用的位置比数组长度小了很多,这意味着进一步增加了碰撞的几率,减慢了查询的效率!这样就会造成空间的浪费。

总结:
也就是说2的N次幂有助于减少碰撞的几率,空间利用率比较大。这样你就明白为什么第一次扩容会从16 ->32了吧?总不会再说32+1=33或者其余答案了吧?至于加载因子,如果设置太小不利于空间利用,设置太大则会导致碰撞增多,降低了查询效率,所以设置了0.75。

27.HasMap的存储和获取原理:

  • 当调用put()方法传递键和值来存储时,先对键调用hashCode()方法,返回的hashCode用于找到bucket位置来储存Entry对象,也就是找到了该元素应该被存储的桶中(数组)。当两个键的hashCode值相同时,bucket位置发生了冲突,也就是发生了Hash冲突,这个时候,会在每一个bucket后边接上一个链表(JDK8及以后的版本中还会加上红黑树)来解决,将新存储的键值对放在表头(也就是bucket中)。
  • 当调用get方法获取存储的值时,首先根据键的hashCode找到对应的bucket,然后根据equals方法来在链表和红黑树中找到对应的值。

28.HasMap的扩容步骤:

  • HashMap里面默认的负载因子大小为0.75,也就是说,当Map中的元素个数(包括数组,链表和红黑树中)超过了16*0.75=12之后开始扩容。将会创建原来HashMap大小的两倍的bucket数组,来重新调整map的大小,并将原来的对象放入新的bucket数组中。这个过程叫作rehashing,因为它调用hash方法找到新的bucket位置。
  • 当然了,上述的扩容机制是比较低效的。所以,我们伟大的JDK开发人员在1.8版本中做了一个扩容效率方面的优化。因为是2的N次幂扩容,所以一个元素要么在原位置不动,要么移动到当前位置+2的N次幂(也就是oldIndex+OldCap的位置)。
    怎么实现呢?
    说白了,就是通过新增的bit位置上是0还是1来判断。
    0则是原位置,1则是oldIndex+OldCap位置。
  • 这个设计确实非常的巧妙,既省去了重新计算hash值的时间,而且同时,由于新增的1bit是0还是1可以认为是随机的,因此resize的过程,均匀的把之前的冲突的节点分散到新的bucket了。这一块是JDK1.8新增的优化点,感兴趣的同学可以去看下源码。
    但是,需要注意的是在多线程环境下,HashMap扩容可能会导致死循环。

29.解决Hash冲突的方法有哪些?

拉链法 (HashMap使用的方法)
线性探测再散列法
二次探测再散列法
伪随机探测再散列法

30.哪些类适合作为HashMap的键?

String和Interger这样的包装类很适合做为HashMap的键,因为他们是final类型的类,而且重写了equals和hashCode方法,避免了键值对改写,有效提高HashMap性能。
为了计算hashCode(),就要防止键值改变,如果键值在放入时和获取时返回不同的hashCode的话,那么就不能从HashMap中找到你想要的对象。

31.ConcurrentHashMap和Hashtable的区别?

ConcurrentHashMap结合了HashMap和Hashtable二者的优势。HashMap没有考虑同步,Hashtable考虑了同步的问题。但是Hashtable在每次同步执行时都要锁住整个结构。

ConcurrentHashMap锁的方式是稍微细粒度的,ConcurrentHashMap将hash表分为16个桶(默认值),诸如get,put,remove等常用操作只锁上当前需要用到的桶。

32.TreeMap有哪些特性?

TreeMap底层使用红黑树实现,TreeMap中存储的键值对按照键来排序。

33.HashSet和TreeSet有哪些区别?

HashSet底层使用了Hash表实现。

  • 保证元素唯一性的原理:判断元素的hashCode值是否相同。如果相同,还会继续判断元素的equals方法,是否为true

TreeSet底层使用了红黑树来实现。

  • 保证元素唯一性是通过Comparable或者Comparator接口实现

34.JVM如何判定一个对象是否应该被回收?(重点掌握)

判断一个对象是否应该被回收,主要是看其是否还有引用。判断对象是否存在引用关系的方法包括引用计数法以及root根搜索方法。
引用计数法:

是一种比较古老的回收算法。原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。垃圾回收时,只需要收集计数为0的对象。此算法最致命的是无法处理循环引用的问题。

root根搜索方法:

root搜索方法的基本思路就是通过一系列可以做为root的对象作为起始点,从这些节点开始向下搜索。当一个对象到root节点没有任何引用链接时,则证明此对象是可以被回收的。以下对象会被认为是root对象:

  • 栈内存中引用的对象
  • 方法区中静态引用和常量引用指向的对象
  • 被启动类(bootstrap加载器)加载的类和创建的对象
  • Native方法中JNI引用的对象。

Java中的类加载机制有了解吗?(重点掌握)

Java中的类加载机制指虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验、转换、解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型。

类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括了:加载、验证、准备、解析、初始化、使用、卸载七个阶段。类加载机制的保持则包括前面五个阶段。

类加载器的分类:

  • 启动类加载器(Bootstrap ClassLoader):
    启动类加载器负责加载存放在JDK\jre\lib(JDK代表JDK的安装目录,下同)下,或被-Xbootclasspath参数指定的路径中的类。
  • 扩展类加载器(ExtClassLoader):
    扩展类加载器负责加载JDK\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.*开头的类)。
  • 应用类加载器(AppClassLoader):
    应用类加载器负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器。

---------------------------------------------多线程篇--------------------------------------

1.多线程创建有哪几种方式?

实现Runable接口和实现Thread类。
除此之外还可以实现Callable接口和线程池来创建线程。

2.谈谈你对volatile关键字的理解。

volatile关键字是一个轻量级的锁,可以保证可见性和有序性,但是不保证原子性。

  • volatile 可以保证主内存和工作内存直接产生交互,进行读写操作,保证可见性
  • volatile 仅能保证变量写操作的原子性,不能保证读写操作的原子性。
  • volatile可以禁止指令重排序(通过插入内存屏障),典型案例是在单例模式中使用。

volatile变量的开销:
volatile不会导致线程上下文切换,但是其读取变量的成本较高,因为其每次都需要从高速缓存或者主内存中读取,无法直接从寄存器中读取变量。
volatile在什么情况下可以替代锁?
volatile是一个轻量级的锁,适合多个线程共享一个状态变量,锁适合多个线程共享一组状态变量。可以将多个线程共享的一组状态变量合并成一个对象,用一个volatile变量来引用该对象,从而替代锁。

3.关键字synchronized和volatile区别以及作用

java多线程中的原子性、可见性、有序性

(1)原子性:是指线程的多个操作是一个整体,不能被分割,要么就不执行,要么就全部执行完,中间不能被打断。

(2)可见性:是指线程之间的可见性,就是一个线程修改后的结果,其他的线程能够立马知道。

(3)有序性:为了提高执行效率,java中的编译器和处理器可以对指令进行重新排序,重新排序会影响多线程并发的正确性,有序性就是要保证不进行重新排序(保证线程操作的执行顺序)。

synchronized和volatile的区别:

volatile本质:是java虚拟机(JVM)当前变量在工作内存中的值是不确定的,需要从主内存中读取;

synchronized则是锁定当前的变量,只有当前线程可以访问到该变量,其他的线程将会被阻塞。

(1)、volatile只能作用于变量,使用范围较小。

         synchronized可以用在变量、方法、类、同步代码块等,使用范围比较广。

(2)、volatile只能保证可见性和有序性,不能保证原子性。

         synchronized都可以包证可见性、有序性、原子性。

(3)、volatile不会造成线程阻塞。

         synchronized可能会造成线程阻塞。

二者作用:

synchronized:
该关键字提供了一种同步锁,被修饰的代码块可以防止被多个线程同时运行,代码块运行时,相当于单线程操作,故能够保证原子性、可见性、有序性

volatile:
volatile只保证可见性和有序性,被volatile修饰的共享变量必须在修改后及时刷新到主存中,并且禁止指令重新排序,故保证可见性和有序性

4.线程的生命周期,线程有哪些状态

1.线程通常有五种状态,创建,就绪,运行,阻塞和死亡状态。
2.阻塞的情况又分为三种:

  • 等待阻塞:运行的线程执行wait()方法,该线程会被释放占用所有资源,JVM会把该线程放入等待池中,进入这个状态后,是不能自动唤醒的,必须依靠其它线程调用notify或者notifyAll方法才能被唤醒,wait是Object类的方法
  • 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中
  • 其它阻塞: 运行的线程执行sleep或join方法,或者发出了i/o请求时,JVM会把该线程置为阻塞状态。当sleep状态超时,join等待线程终止或者超时,或者I/O处理完毕时,线程重新转入就绪状态,sleep时Thread类的方法。

1.新建状态(new):新创建了一个线程对象
2.就绪状态(Runnable):线程对象创建后,其它线程调用了该对象的start方法,该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3.运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4.阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行,直到线程进入就绪状态,才有机会转到运行状态。
5.死亡状态(Dead):线程执行完了或者因异常退出了run方法,该线程结束生命周期

5.Synchronized 和 Lock 区别

1、Synchronized 内置的Java关键字, Lock 是一个Java类
2、Synchronized 无法判断获取锁的状态,Lock 可以判断是否获取到了锁
3、Synchronized 会自动释放锁,lock 必须要手动释放锁!如果不释放锁,死锁
4、Synchronized 线程 1(获得锁,阻塞)、线程2(等待,傻傻的等);Lock锁就不一定会等待下去;
5、Synchronized 可重入锁,不可以中断的,非公平;Lock ,可重入锁,可以判断锁,非公平(可以自己设置);
6、Synchronized 适合锁少量的代码同步问题,Lock 适合锁大量的同步代码!

6.Java各种锁机制

这里有总结全部锁的机制

7.sleep(),wait(),join(),yield()的区别

1.锁池
所有需要竞争同步锁的线程会被放在锁池中,比如当前对象的锁已经被其中一个线程得到,则其它线程需要在这个锁池进行等待,当前面的线程释放同步锁后锁池中的线程去竞争同步锁,当某个线程得到后会进入就绪队列等待cpu资源分配
2.等待池
当我们调用wait()方法后,线程会放到等待池当中,等待池的线程是不会去竞争同步锁,只有调用了notify()或notifyAll()后等待池的线程才会开始去竞争锁,notify是随机从等待池选出一个线程放到锁池,而notifyAll()是将等待池的所有线程放到锁池中

1.sleep是Thread类的静态本地方法,wait则是Object类的本地方法。
2.sleep方法不会释放lock,但是wait会释放,而且会加入到等待队列中。

sleep就是把cou的执行资格和执行权释放出去,不再运行此线程,当定时时间结束再取回Cpu资源,参与cpu的调度,获取到cpu资源后就可以继续运行了,而如果sleep时该线程有锁,namesleep不会释放这个锁,而是把锁带着进入了冻结状态,也就是说其它需要这个锁的线程根本不可能获取到这个锁,也就是说无法执行程序,,如果在睡眠期间其它线程调用了这个线程的interrupt方法,name这个线程也会抛出interruptexception异常返回,这点和wait是一样的。

3.sleep方法不依赖于同步器synchronized,但是wait需要依赖synchornized关键字。
4.sleep不需要被唤醒(休眠之后推出阻塞),但是wait需要(不指定时间需要被别人中断)。
5.sleep一般用于当前线程休眠,或者轮循暂停操作,wait则多用于多线程之间的通信。
6.sleep会让出cpu执行时间且强制上下文切换,而wait则不一定,wait后可能还是有机会重新竞争到锁继续执行的。
yield()执行后线程直接进入就绪状态,马上释放了cpu的执行权,但是依然保留了cpu的执行资格,所以有可能cpu下次进行线程调度还会让这个线程获取到执行权继续执行。
join()执行后线程进入阻塞状态,例如在线程B中调用线程A的join(),那线程B会进入到阻塞队列,直到线程A结束或中断线程。

8.说说你对线程安全的理解

不是线程安全,应该是内存安全,堆是共享内存,可以被所有线程访问

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

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

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

栈是每个线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立,因此,栈是线程安全的,操作系统在切换线程的时候会自动切换栈。栈空间不需要在高级语言里面显示的分配和释放。
目前主流操作系统都是多任务的,即多个进程同时运行,为了保证安全,每个进程只能访问分配给自己的内存空间,而不能访问别的进程的,这是由于操作系统保障的。
在每个进程的内存空间都会有一块特殊的公共区域,通常称为堆(内存)。进程内的所有线程都可以访问到该区域,这就是造成问题的潜在原因。

9.Thread,Runable的区别

Thread和Runnable的实质是继承关系,没有可比性。无论使用Runnable还是Thread,都会new Thread,然后执行run方法。用法上,如果有复杂的线程操作需求,那就选择继承Thread,如果只是简单的执行一个任务,那就实现runnable。

10.JUC编程常用的辅助类

CountDownLatch

CountDownLatch是一个倒计时协调器,它可以实现一个或者多个线程等待其余线程完成一组特定的操作之后,继续运行。

CyclicBarrier

CyclicBarrier是一个栅栏,可以实现多个线程相互等待执行到指定的地点,这时候这些线程会再接着执行,在实际工作中可以用来模拟高并发请求测试。

Semaphore

一个计数信号量:在概念上,信号量维持一组许可证,如果有必要,每个acquire()都会阻塞,知道许可证可用,然后才能使用它

  • semaphore.acquire() 获得,假设如果已经满了,等待,等待被释放为止!
  • semaphore.release(); 释放,会将当前的信号量释放 + 1,然后唤醒等待的线程!
  • 作用: 多个共享资源互斥的使用!并发限流,控制最大的线程数!

11.Atmoic有了解吗?

AtomicInteger类提供了getAndIncrement和incrementAndGet等原子性的自增自减等操作。Atomic等原子类内部使用了CAS来保证原子性。

12.JUC四大函数接口

function
函数型接口,有一个输入参数,有一个输出
predicate
段定型接口:有一个输入参数,返回值只能是布尔值
consumer
消费型接口:只有输入,没有返回值
supplier
供给型接口:没有参数,只有返回值

13.说说你对守护线程的理解

守护线程:为所有非守护线程提供服务的线程,任何一个守护线程是整个JVM中所有非守护线程的保姆。
1.线程分为用户线程和守护线程。
2.虚拟机必须确保线程执行完毕。
3.虚拟机不用等待守护线程执行完毕。
如:后台记录操作日志,监控内存,垃圾回收等等。

14.ThreadLocal的原理和使用场景

每一个Thread对象均含有一个ThreadLocalMap类型的成员变量threadLocals,它存储本线程中所有ThreadLocal对象及其对应的值。
ThreadLocal由一个个Entry对象构成
Entry继承自 WeakReference<ThreadLocal<?>>,一个Entry由ThreadLocal和Object构成,由此可见,Entry的key是ThreadLocal对象,并且是一个弱引用。当没有指向key的强引用后,该key就会被垃圾收集器回收
当执行set方法后,ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象,再以当前ThreadLocal对象为key,将值存储进ThreadLocalMap对象中。
get方法执行过程类似,获取对应的value
由于每一条线程均含有各自私有的ThreadLocalMap容器,这些容器互相独立互不影响,因此不会存在线程安全性问题,从而也无需使用同步机制来保证多条线程访问容器的互斥性。
使用场景:
1.在进行对象跨层传递的到时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
2.线程间数据隔离
3.进行实物操作,用于存储线程事务信息。
4.数据库连接,Session会话管理。

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

15.ThreadLocal内存泄漏原因,如何避免

  • 每次使用完ThreadLocal都调用它的remove()方法清楚数据
  • 将ThreadLocal的弱引用访问到Entry的value值,进而清除掉。

16.并发,并行,串行的区别

  • 串行在时间上不可能发生重叠,前一个任务没搞定,下一个任务就只能等着。
  • 并行在时间上是重叠的,两个任务在同一时刻互不干扰的同时执行。
  • 并发允许两个任务彼此干扰。统一时间点,只有一个任务运行,交替执行。

17.并发的三大特性

  • 原子性

原子性指的是一个操作是不可中断的,即使是在多线程环境下,一个操作一旦开始就不会被其他线程影响。可以通过 synchronized和Lock实现原子性,volatile不能保证原子性。

  • 可见性

共享变量在不同线程的工作内存可见,volatile可以解决可见性问题(能否及时可见),不加volatile也可能可见,但不一定及时。synchronized和Lock也可以保证可见性,因为它们可以保证任一时刻只有一个线程能访问共享资源,并在其释放锁之前将修改的变量刷新到内存中。

  • 有序性

有序性是指对于单线程的执行代码,我们总是认为代码的执行是按顺序依次执行的,即使指令重排也要保证执行结果不受影响。

18.线程池的三大方法

newSingleThreadExecutor()

  • 创建单个线程
  • 便于实现单(多)生产者-消费者模式

newFixedThreadPool()

  • 固定大小的线程池
  • 当线程池大小达到核心线程池大小,就不会增加也不会减小工作者线程的固定大小的线程池

newCachedThreadPool() 课伸缩的,遇强则强,遇弱则弱

  • 核心线程池大小为0,最大线程池大小不受限,来一个创建一个线程
  • 适合用来执行大量耗时较短且提交频率较高的任务

19.为什么用线程池?解释下线程池七大参数?四大拒绝策略?

1.降低资源消耗,提高线程利用率,降低创建和销毁线程的消耗
2.提高响应速度,任务来了,直接有线程可用可执行,而不是先创建线程,再执行。
3.提高线程的可管理性,线程是稀缺资源,使用线程池可以统一分配调优监控

  • corePoolSize: 代表核心线程数,也就是正常情况下创建工作的线程数,这些线程数创建后并不会消除,而是一种常驻线程。
  • maxinumPoolSize 代表最大线程数,它与核心线程数相对应,表示最大允许被创建的线程数,比如当前任务较多,将核心线程数都用完了,还无法瞒住需求时,此时就会创建新的线程,但是线程池内线程总数不会超过最大线程数
  • keepAliveTime 表示超过核心线程数之外的线程和空闲存活时间,也就是核心线程不会消除,但是超出核心线程数的部分线程如果空闲一定的时间则会被消除,可以通过setKeepAliveTime来设置空闲时间
    unit:keepAliveTime的时间单位
  • workQueue 用来存放待执行的任务,假设我们现在核心线程都已被使用,还有任务进来则全部放入队列,直到整个队列被放满但任务还在持续进入,则会开始创建先的线程。
  • ThreadFactory,实际上是一个线程工厂,用来生产线程执行任务。
  • Hander 任务拒绝策略

AbortPolicy() // 银行满了,还有人进来,不处理这个人的,抛出异常
CallerRunsPolicy() //哪里来的去哪里
DiscardPolicay() //队列满了 丢掉任务 不会抛出异常
DiscardOldestPolicy() //队列满了,尝试去和最早的竞争,也不会抛出异常

20.简述线程池处理流程

在这里插入图片描述

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

1.一般的队列只能保证作为一个有限长度的缓冲区,如果超出了缓冲长度,就无法保留当前的任务了,阻塞队列通过阻塞可以保留住当前想要继续入队的任务。
阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程进入wait状态,释放cpu资源。
阻塞队列自带阻塞和唤醒的功能,不需要额外处理,无任务执行时,线程池利用阻塞队列的take方法挂起,从而维持核心线程的存活,不至于一直占用cpu资源。
2.在创建新线程的时候,是要获取全局锁的,这个时候其它的就得阻塞,影响了整体效率。

22.常见的阻塞队列

ArrayBlockingQueue:

  • 内部使用一个数组作为其存储空间,数组的存储空间是预先分配的
  • 优点是 put 和 take操作不会增加GC的负担(因为空间是预先分配的)
  • 缺点是 put 和 take操作使用同一个锁,可能导致锁争用,导致较多的上下文切换。
  • ArrayBlockingQueue适合在生产者线程和消费者线程之间的并发程序较低的情况下使用。

LinkedBlockingQueue:

  • 是一个无界队列(其实队列长度是Integer.MAX_VALUE)
  • 内部存储空间是一个链表,并且链表节点所需的存储空间是动态分配的
  • 优点是 put 和 take 操作使用两个显式锁(putLock和takeLock)
  • 缺点是增加了GC的负担,因为空间是动态分配的。
  • LinkedBlockingQueue适合在生产者线程和消费者线程之间的并发程序较高的情况下使用。

SynchronousQueue:

SynchronousQueue可以被看做一种特殊的有界队列。生产者线程生产一个产品之后,会等待消费者线程来取走这个产品,才会接着生产下一个产品,适合在生产者线程和消费者线程之间的处理能力相差不大的情况下使用。

  • 我们前边介绍newCachedThreadPool时候说,这个线程池来一个线程就创建一个,这是因为其内部队列使用了SynchronousQueue,所以不存在排队。

------------------------------------Mysql篇--------------------------------------

1.索引的基本原理

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

  1. 把创建了索引的列的内容进行排序
  2. 对排序结果生成倒排表
  3. 在倒排表内容上拼上数据地址链
  4. 在查询的时候,先拿到倒排表内容,再取出数据地址链,从而拿到具体数据

2.为什么底层数据结构使用B+树,而不是B树?

  • B+树是B树的变种,B+树的非叶子节点只用来保存索引,不存储数据,所有的数据都保存在叶子节点;而B树的非叶子节点也会保存数据。这样就使得B+树的查询效率更加稳定,均为从根节点到叶子节点的路径。
  • B+树的内部结点并没有指向关键字具体信息的指针,因此其内部结点相对B 树更小,同样空间可以读入更多的节点,所以B+树的磁盘读写代价更低。

3.mysql聚簇和非聚簇索引的区别

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

非聚簇索引(普通索引)

  • 叶子节点不存储数据、存储的是数据行地址,也就是说根据索引查找到数据行的位置
    再取磁盘查找数据
  • 聚簇索引检索效率高,对数据新增/修改/删除的影响比较大
  • 非聚簇索引检索效率比聚簇索引低,,对数据新增/修改/删除的影响很小

4.简述MyISAM和InnoDB的区别

InnoDb:支持事务,数据行锁定,外键约束,不支持全文索引,表空间较大,约两倍
Myisam:不支持事务,行锁,外键。支持表锁,支持全文索引,表空间较小

5.MySQL事务的隔离级别, 分别有什么特点

  1. 读未提交((Read Uncommitted): 一个事务还没提交时, 它做的变更就能被别的事务看到.
  2. 读已提交(Read Committed): 一个事务提交之后, 它做的变更才会被其他事务看到.
  3. 可重复读(Repeatable Read): 一个事务执行过程中看到的数据, 总是跟这个事务在启动时看到的数据是一致的. 当
    然在可重复读隔离级别下, 未提交变更对其他事务也是不可见的.
  4. 序列化 (串行化)(Serializable): 对于同一行记录, 读写都会加锁. 当出现读写锁冲突的时候, 后访问的事务必须等前一个
    事务执行完成才能继续执行.

事务的隔离级别越高,对数据的完整性和一致性保证越佳,但是对并发操作的影响也越大。MySQL事务默认隔离级别是可重复读。

6.做过哪些索引的优化?

1.尽量使用主键查询: 聚簇索引上存储了全部数据, 相比普通索引查询, 减少了回表的消耗.
2.MySQL5.6之后引入了索引下推优化, 通过适当的使用联合索引, 减少回表判断的消耗
3.若频繁查询某一列数据, 可以考虑利用覆盖索引避免回表.
4.联合索引将高频字段放在最左边.

7.订单数据量大,查询缓慢,怎么处理?

分库分表
由于历史订单使用率并不高, 高频的可能只是近期订单, 因此, 将订单表按照时间进行拆分, 根
据数据量的大小考虑按月分表或按年分表. 订单ID最好包含时间(如根据雪花算法生成), 此时既能根据订
单ID直接获取到订单记录, 也能按照时间进行查询

8.三大范式:

1.保证每列原子性,每列都不可再分
2.在第一范式基础上,保证一个表只做一个事情
3.确保数据表中的每一个列数据都和主键直接关联,而不能间接关联

9.建立索引准则

  • 索引不是越多越好
  • 不要对经常变动的数据加索引。
  • 小数据量的表建议不要加索引
  • 符合索引的最左前缀原则
  • 索引一般应加在查找条件的字段

9.说说事务的ACID?

原子性:事务的所有操作,要么全完成,要么全部不完成
隔离性:隔离状态执行事务,使他们好像在系统给定时间内执行的唯一操作
一致性:事务必须保持系统处于一致的状态
持久性:在事务完成后,该事务对数据库所作的更改便持久的保存在数据库中,并不会回滚

10.如果不做控制,多个事务并发操作数据库会产生哪些问题?

脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据。

不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,
对数据作了更新并提交,导致事务A多次读取同一数据时,结果不一致。

幻读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了修改或者删除操作并提交,
导致事务A多次读取同一数据时,莫名的多出了一些之前不存在数据,或者莫名的丢了一些数据。像发生了幻觉一样。

  • 不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。
  • 解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表。

11.MySQL中的锁机制?

MySQL数据库的锁分为表级锁和行级锁。从数据库的角度看,行级锁又可以分为独占锁和共享锁。

  • 独占锁锁定的资源只允许进行锁定操作的程序使用,其它任何对它的操作均不会被接受。执行数据更新命令,即INSERT、UPDATE 或DELETE 命令时,MySQL会自动使用独占锁。但当对象上有其它锁存在时,无法对其加独占锁。独占锁一直到事务结束才能被释放。
  • 共享锁顾名思义,那就是其锁定的资源可以被其它用户读取,但其它用户不能修改。如果在select查询语句中要手动加入共享锁,那么对应的SQL语句为:select … lock in share mode

注意:
一个事务在一行数据上加入了独占锁,那么其余事务不可以在该数据行上加入任何锁。也就是说加过排他锁的数据行在其他事务种是不能修改数据的,也不能通过for update和lock in share mode锁的方式查询数据,但可以直接通过select …from…查询数据,因为普通查询没有任何锁机制。

12.MySQL中的死锁怎么造成的?

  • MySQL中的死锁主要是多个事务使用行级锁对某行数据加锁造成的
  • MyISAM不支持行级锁,所以MySQL中的死锁主要是在说InnoDB存储引擎的死锁。

业务逻辑上的死锁解决方案:

  • 指定锁的获取顺序
  • 大事务拆分成各个小事务
  • 在同一个事务中,一次锁定尽量多的资源,减少死锁概率
  • 给表建立合适的索引以及降低事务的隔离级别等

13.mysql的回表,覆盖索引,索引下推,最左前缀原则是什么?

  • 如果语句是 select * from User where id=3,即主键查询方式,则只需要搜索 主键索引树。
  • 如果语句是 select * from User where uid=23,即普通索引查询方式,则需要先搜索 普通索引树,得到其对应的主键值为 3,再到主键索引树搜索一次。这个过程称为回表。

覆盖索引

如果在普通索引树上的查询已经直接提供了结果,不需要回表操作,这样的普通索引叫做覆盖索引。覆盖索引的使用可以显著提高查询效率,是常见的MySQL性能优化手段。

索引的最左前缀原则:

在联合索引的情况下,不需要索引的全部定义,只要满足最左前缀,就可以利用索引来加快查询速度。这个最左前缀可以是联合索引的最左 N 个字段,也可以是字符串索引的最左 M 个字符。最左前缀原则的利用也可以显著提高查询效率,是常见的MySQL性能优化手段。

索引下推:

在MySQL5.6之前,只能从根据最左前缀查询到ID开始一个个回表。到主键索引上找出数据行,再对比字段值。MySQL5.6引入的索引下推优化,(联合索引前提)可以在索引遍历过程中,对索引中包含的其余字段先做判断,直接过滤掉不满足条件的记录,减少回表次数,提升查询效率。

--------------------------------------框架篇--------------------------------------

1.什么是mybatis ,它的工作流程是怎样的?

1.mybatis是一款优秀的半自动化的ORM持久层框架(ORM:对象关系映射)
2.mybatis几乎避免了所有的JDBC代码和手动设置参数以及获取结果集的过程
3.mybatis可以使用简单的xml或注解来配置和映射原生信息,将接口和Java的实体类映射成数据库中的记录
mybatis优点

1.灵活,不会对应用程序或者现有的数据库设计强加任何影响。
2.sql写在xml中,便于统一管理和优化,通过sql语句可以满足操作数据库的所有需求。
3.解除sql代码与程序的耦合,通过提供dao层,将业务逻辑和数据访问逻辑分离,是系统设计更清晰,更易于维护
4.提供xml标签,支持编写动态sql

mybatis缺点

1、SQL 语句的编写工作量较大, 尤其当字段多、关联表多时, 对开发人员编写SQL 语句的功底有一 定要求。
2、SQL语句依赖于数据库, 导致数据库移植性差, 不能随意更换数据库

2.mybatis一二级缓存

一级缓存:(也叫本地缓存)
1.与数据库同一会话期间查询到的数据会放到本地缓存中
2.以后如果需要获取相同的数据,直接从缓存中拿,没必要去查询数据库
二级缓存:(也叫全局缓存,一级缓存作用域低,所以诞生了二级缓存)
1.基于namespace级别的缓存,一个名称空间,对应一个二级缓存
2.如果当前会话关闭,这个会话对应的一级缓存就会被保存到二级缓存中
3.新的会话查询信息,就可以从二级缓存中获取内容
4.不同的mapper查出的数据会放在自己对应的缓存中(map)中
要开启二级缓存,你需要在你的 SQL 映射文件中手动添加一行:< cache/>
工作流程:

1.通过SqlSessionFactoryBuilder创建SqlSessionFactory对象
2.通过SqlSessionFactory创建SqlSession对象
3.通过SqlSession拿到Mapper代理对象
4.通过MapperProxy调用Mapper中增删改查的方法

3.Spring是什么?

Spring是一个轻量级的,非入侵式的容器框架,用来装java对象,中间层框架可以起一个连接作用,比如把Struts和Hibernate粘合在一起运用,可以让我们的企业开发更快,更简洁

1.从大小和开销两方面都是轻量级的
2.通过控制反转的技术达到松耦合的目的
3.提供面向切面编程的支持,允许通过分离应用的业务逻辑与系统服务进行内聚性的开发。
4.包含并管理应用对象的配置和生命周期(这个意义上是个容器)。
5.将简单的组件配置,组合成为复杂的应用,(这个意义上是个框架)。

4.谈谈你对Aop的理解

Aop意为面向切面编程,通过预编译的方式和运行期间动态代理实现程序功能的统一维护的一种技术,Aop是oop的延续,是函数式编程的衍生泛型,利用Aop可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑之间各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发效率。

5.谈谈你对IOC的理解

IOC是一种编程的思想,有主动的编程变为被动的接收

控制反转就是一个对象有Spring容器设置的,这个过程就叫控制反转。
控制:谁来控制对象的创建,传统应用程序的对象是由程序本身控制创建的,使用Spring后,对象有Spring创建。
反转:程序本身不创建对象,而变成被动的接收对象。
依赖注入就是实现IOC的方法,就是利用set方法进行注入的。
依赖:指Bean对象的创建依赖于容器,Bean对象的依赖资源。
注入:指Bean对象所依赖的资源,由容器来设置和装配

6.BeanFactory和ApplicationContext有什么区别?

BeanFactory是ApplicationContext的子接口
ApplicationContext提供了更完整的功能:
1.继承MessageSource,因此支持国际化。
2.统一的资源文件访问方式。
3.提供在监听器中注册bean的事件。
4.同时加载多个文件配置。
5.载入多个(有继承关系的上下文),使得每一个上下文都专注于一个特定的层次,比如应用的web层。

  • 加载方式 BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用
    getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置
    问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用 getBean方法才会抛出异常。
    ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动
    时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。
    ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需
    要的时候,你就不用等待,因为它们已经创建好了。
  • 创建方式 BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用 ContextLoader。
  • 注册方式 BeanFactory和ApplicationContext都支持BeanPostProcessor、 BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而
    ApplicationContext则是自动注册。

ApplicationContext 三种常见的实现方式:

  • FileSystemXmlApplicationContext:此容器从一个XML文件中加载Bean的定义,XML Bean 配置文件的全路径名必须提供给它的构造函数。
  • ClassPathXmlApplicationContext:此容器也从一个XML文件中加载Bean的定义,需要正确设置classpath因为这个容器将在classpath里找Bean配置。
  • WebXmlApplicationContext:此容器加载一个XML文件,定义了一个WEB应用的所有Bean。

7.描述一下Spring Bean的生命周期

1.解析类得到BeanDefintion
2.如果当前类有多个构造方法,则要推断构造方法
3.确定好构造方法后,进行实例化得到一个对象
4.对对象中加了@Autowired (@Inject @Resource等)注解的属性进行属性填充
5.回调Aware方法,比如BeanNameAware,BeanFactoryAware
6.调用BeanPostProcessor 的初始化前的方法
7.调用初始化方法(实现了InitializingBean接口)
8.调用BeanPostProcessor 的初始化后的方法,在这里会进行AOP
9.如果当前创建的bean是单例的,则会把bean放入单例池
10使用bean
11.Spring容器关闭时调用destory() 方法(实现了DisposableBean)
详情流程图:SpringBean生命周期流程

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

1.singleton :bean在每个Spring ioc 容器中只有一个实例。

2.prototype:一个bean的定义可以有多个实例。

3.request:每次http请求都会创建一个bean,该作用域仅在基于web的Spring ApplicationContext情形下有效。

4.session:在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。

5.global-session:在一个全局的HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。

9.Spring框架中的单例Bean是线程安全的么?Spring如何处理线程并发问题?

不是,Spring框架中的单例bean不是线程安全的。
spring 中的 bean 默认是单例模式,spring 框架并没有对单例 bean 进行多线程的封装处理。实际上大部分 spring bean 是无状态的(比如 dao 类),在某种程度上来说 bean 也是安全的,但如果 bean 有状态的话(比如 view model )就要开发者自己去保证线程安全了,最简单的就是改变 bean 的作用域,把“singleton”变更为“prototype”,这样请求 bean 相当于 new Bean()了, 保证线程安全了。

有状态就是有数据存储功能。
无状态就是不会保存数据。
Spring如何处理线程并发问题?
一般只有无状态的Bean才可以在多线程下共享,大部分是无状态的Bean。当存有状态的Bean的时候,spring一般是使用ThreadLocal进行处理,解决线程安全问题。

ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。
同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程获取锁,没获得锁的线程则需要排队。而ThreadLocal采用了“空间换时间”的方式。
ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,所以没有相同变量的访问冲突问题。所以在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。

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

1.简单工厂 又叫做静态工厂方法(StaticFactory Method)模式,但不属于23种GOF设计模式之一。 简单工厂模式的实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类,Spring中的BeanFactory就是简单工厂模式的体现。
2.工厂方法(Factory Method)

定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory
Method使一个类的实例化延迟到其子类。Spring中的FactoryBean就是典型的工厂方法模式。

3.单例(Singleton) 保证一个类仅有一个实例,并提供一个访问它的全局访问点。

Spring中的单例模式完成了后半句话,即提供了全局的访问点BeanFactory。但没有从构造器级别去控制单例,这是因为Spring管理的是是任意的Java对象。

4.适配器模式(Adapter) 将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

5.装饰器模式(Decorator) 动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。 Spring中用到的包装器模式在类名上有两种表现:一种是类名中含有Wrapper,另一种是类名中含有Decorator。基本上都是动态地给一个对象添加一些额外的职责。

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

Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。Spring只提供统一事务管理接口,具体实现都是由各数据库自己实现,数据库事务的提交和回滚是通过binlog或者undo log实现的。Spring会在事务开始时,根据当前环境中设置的隔离级别,调整数据库隔离级别,由此保持一致。

spring支持编程式事务管理和声明式事务管理两种方式:
①编程式事务管理使用TransactionTemplate。
②声明式事务管理建立在AOP之上的。其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前启动一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

声明式事务最大的优点就是不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明或通过@Transactional注解的方式,便可以将事务规则应用到业务逻辑中,减少业务代码的污染。唯一不足地方是,最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。

Spring 中的隔离级别:
① ISOLATION_DEFAULT:这是个 PlatfromTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别。
② ISOLATION_READ_UNCOMMITTED:未提交读,允许另外一个事务可以看到这个事务未提交的数据。
③ ISOLATION_READ_COMMITTED:已提交读,不可重复读,保证一个事务修改的数据提交后才能被另一事务读取,而且能看到该事务对已有记录的更新。
④ ISOLATION_REPEATABLE_READ:可重复读,保证一个事务修改的数据提交后才能被另一事务读取,但是不能看到该事务对已有记录的更新。
⑤ ISOLATION_SERIALIZABLE:一个事务在执行的过程中完全看不到其他事务对数据库所做的更新。

12.Spring事务什么时候会失效?

Spring事务的原理是Aop,进行了切面增强,那么失效的根本原因就是这个Aop不起作用了常见情况如下:
1.发生自调用,类里面使用this调用本类的方法(this通常省略),此时这个this对象不是代理类,而是UserService对象本身!解决方法:让那么this变成UserService的代理类即可
2.方法不是public,@transactional只能作用public上,否则事务不会生效,如果要在非public上,可以开启AspectJ代理模式
3.数据库不支持事务(Myisam)
4.没有被Spring管理
5.异常被吃掉,事务不会回滚(或者排除的异常没有被定义,默认为RuntimeException)

13.什么是Bean的自动装配,有哪些方式?

开启自动装配,只需要在xml配置文件中定义“autowire”属性。
autowire属性有五种装配的方式:

  • no – 缺省情况下,自动配置是通过“ref”属性手动设定
  • byName-根据bean的属性名称进行自动装配
  • byType-根据bean的类型进行自动装配。
  • constructor-类似byType,不过是应用于构造器的参数。如果一个bean与构造器参数的类型形 同,则进行自动装配,否则导致异常。
  • autodetect-如果有默认的构造器,则通过constructor方式进行自动装配,否则使用byType方式 进行自动装配。

14.Spring,SpringMVC,SpringBoot有什么区别?

spring是一个IOC容器,用来管理Bean,使用依赖注入实现控制反转,可以很方便的整合各种框架,提
供AOP机制弥补OOP的代码重复问题、更方便将不同类不同方法中的共同处理抽取成切面、自动注入给
方法执行,比如日志、异常等
springmvc是spring对web框架的一个解决方案,提供了一个总的前端控制器Servlet,用来接收请求,
然后定义了一套路由策略(url到handle的映射)及适配执行handle,将handle结果使用视图解析技术
生成视图展现给前端
springboot是spring提供的一个快速开发工具包,让程序员能更方便、更快速的开发spring+springmvc
应用,简化了配置(约定了默认配置),整合了一系列的解决方案(starter机制)、redis、
mongodb、es,可以开箱即用

15.SpringMvc的工作流程

在这里插入图片描述

1.用户发送请求到前端控制器dispatchservlet
2.前端控制器请求handlermapping获取handler
3.最终通过handlerexcutionChain得到的handler给前端控制器
4.前端控制器请求handlerAdatper执行handler
5.handlerAdapter根据handler规则执行不同的handler
6.Handler执行完毕后返回一个modelAndView给handlerAdapet
7.handlerAdapter返回modelAndView给前端控制器
8.前端控制器请求视图解析器解析
9.试图解析器进行国际化处理后返回一个view给前端控制器
10.前端控制器渲染视图将model数据转为response响应给视图view
11.前端控制器响应结果

16.SrpingMVC的九大组件

1.SpringMVC中的Servlet一共有三个层次,分别是HttpServletBean、FrameworkServlet和 DispatcherServlet。
2. HttpServletBean直接继承自java的HttpServlet,其作用是将Servlet中配置的参数设置到相应的属性
3. FrameworkServlet初始化了WebApplicationContext,DispatcherServlet初始化了自身的9个组件。

在学习9个组件之前,我们需要先了解Handler的概念,也就是处理器。它直接应对着MVC中的C也就是Controller层,它的具体表现形式有很多,可以是类,也可以是方法。在Controller层中@RequestMapping标注的所有方法都可以看成是一个Handler,只要可以实际处理请求就可以是Handler。

【1. HandlerMapping】

是用来查找Handler的。在SpringMVC中会有很多请求,每个请求都需要一个Handler处理,具体接收到一个请求之后使用哪个Handler进行处理呢?这就是HandlerMapping需要做的事。
【2. HandlerAdapter】

从名字上看,它就是一个适配器。因为SpringMVC中的Handler可以是任意的形式,只要能处理请求就ok,但是Servlet需要的处理方法的结构却是固定的,都是以request和response为参数的方法。如何让固定的Servlet处理方法调用灵活的Handler来进行处理呢?这就是HandlerAdapter要做的事情。
小结:Handler是用来干活的工具;HandlerMapping用于根据需要干的活找到相应的工具;HandlerAdapter是使用工具干活的人。
【3. HandlerExceptionResolver】

其它组件都是用来干活的。在干活的过程中难免会出现问题,出问题后怎么办呢?这就需要有一个专门的角色对异常情况进行处理,在SpringMVC中就是HandlerExceptionResolver。具体来说,此组件的作用是根据异常设置ModelAndView,之后再交给render方法进行渲染。
【4. ViewResolver】

ViewResolver用来将String类型的视图名和Locale解析为View类型的视图。View是用来渲染页面的,也就是将程序返回的参数填入模板里,生成html(也可能是其它类型)文件。这里就有两个关键问题:使用哪个模板?用什么技术(规则)填入参数?这其实是ViewResolver主要要做的工作,ViewResolver需要找到渲染所用的模板和所用的技术(也就是视图的类型)进行渲染,具体的渲染过程则交由不同的视图自己完成。
【5. RequestToViewNameTranslator】

ViewName是根据ViewName查找View,但有的Handler处理完后并没有设置View也没有设置ViewName,这时就需要从request获取ViewName了,如何从request中获取ViewName就是RequestToViewNameTranslator要做的事情了。RequestToViewNameTranslator在Spring
MVC容器里只可以配置一个,所以所有request到ViewName的转换规则都要在一个Translator里面全部实现。 【6.
LocaleResolver】

解析视图需要两个参数:一是视图名,另一个是Locale。视图名是处理器返回的,Locale是从哪里来的?这就是LocaleResolver要做的事情。LocaleResolver用于从request解析出Locale,Locale就是zh-cn之类,表示一个区域,有了这个就可以对不同区域的用户显示不同的结果。SpringMVC主要有两个地方用到了Locale:一是ViewResolver视图解析的时候;二是用到国际化资源或者主题的时候。
【7. ThemeResolver】

用于解析主题。SpringMVC中一个主题对应一个properties文件,里面存放着跟当前主题相关的所有资源、如图片、css样式等。SpringMVC的主题也支持国际化,同一个主题不同区域也可以显示不同的风格。SpringMVC中跟主题相关的类有
ThemeResolver、ThemeSource和Theme。主题是通过一系列资源来具体体现的,要得到一个主题的资源,首先要得到资源的名称,这是ThemeResolver的工作。然后通过主题名称找到对应的主题(可以理解为一个配置)文件,这是ThemeSource的工作。最后从主题中获取资源就可以了。
【8. MultipartResolver】

用于处理上传请求。处理方法是将普通的request包装成MultipartHttpServletRequest,后者可以直接调用getFile方法获取File,如果上传多个文件,还可以调用getFileMap得到FileName->File结构的Map。此组件中一共有三个方法,作用分别是判断是不是上传请求,将request包装成MultipartHttpServletRequest、处理完后清理上传过程中产生的临时资源。
【9. FlashMapManager】

用来管理FlashMap的,FlashMap主要用在redirect中传递参数。

17.SpringBoot自动配置原理

在这里插入图片描述

18.如何理解Spring boot中的Starter

使用Spring+SpringMVC使用,如果需要引入mybatis等框架,需要到xml中定义mybatis需要的bean
starter就是定义一个starter的jar包,写一个@Configuration配置类,将这些bean定义在里面,然后在starter包的META-INF/spring.factories中写入该配置类,springboot会按照约定来加载该配置类
开发人员只需要将相应的starter包依赖进应用,进行相应的属性配置(使用默认配置时,不需要配置),就可以直接进行代码开发,使用对应的功能了,比如高mybatis-spring-boot-start ,spring-boot-starter-redis

19.什么是嵌入式服务器?为什么要使用嵌入式服务器?

节省了下载安装tomcat,应用不需要再打war包,然后放到webapp目录下在运行,只需要一个安装了java的虚拟机,就可以直接在上面部署应用程序
springboot已经内置了tomcat.jar,运行main方法时会去启动tomcat,并利用tomcat的spi机制加载springmvc

20.#{}和${}的区别

#{}是预编译处理,是占位符,${}是字符串替换,是拼接符。
mybatis在处理#{}时,会将sql中的#{}替换成?号,调用preparedStatement来赋值;
mybatis在处理 ${}时,会把 ${}替换成变量的值,调用Statement来赋值;
#{}的变量替换是在DBMS中,变量替换后, #{}对应的变量自动加上单引号
${}的变量替换是在DBMS外,变量替换后, ${}对应的变量不会加上单引号
使用#{}可以有效的防止SQL注入,提高系统的安全性。

21.如何实现一个IOC容器。

1.配置文件配置包扫描路径
2.递归包扫描获取.class文件
3.反射,确定需要交给IOC管理的类
4.对需要注入的类进行依赖注入

  • 配置文件中指定需要扫描的包路径;
  • 定义一些注解,分别表示访问控制层,业务服务层,数据持久层,依赖注入注解,获取配置文件注解;
  • 从配置文件中获取需要扫描的包路径,获取到当前路径下的文件信息及文件夹信息,我们将当前路径下所有意.class结尾的文件添加到一个set集合进行存储
  • 遍历这个set集合,获取在类上有指定注解的类,并将其交给IOC容器,定义一个安全的Map来存储这些对象
  • 遍历这个IOC容器,获取到每一个类的实例,判断里面是有依赖其他类的实例,然后进行递归注入。

22.SpringBoot的核心注解:

启动类上面的注解是@SpringBootApplication,它也是 Spring Boot 的核心注解,主要组合包含了以下 3 个注解:

  • @SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。
  • @EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项。如关闭数据源自动配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。
  • @ComponentScan:Spring组件扫描。

23.Springc创建bean的几种方式?

1.使用构造器实例化Bean
2.使用静态工厂方式实例化Bean
3.使用实例工厂方法实例化Bean
4.用 setter 方式

23.listener、 filter、servlet 加载顺序

ServletContext -> listener -> filter -> servlet

24.Spring MVC的异常处理?

可以将异常抛给Spring框架,由Spring框架来处理;我们只需要配置简单的异常处理器(实现HandlerExceptionResolver接口的自定义异常处理器),在异
常处理器中添视图页面即可。

-------------------------------------------Redis篇-------------------------------------

1.什么是Redis?

1.Redis 是完全开源免费的, 遵守 BSD 协议, 是一个高性能的 key-value 数据库。
2.Redis 支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
3.Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
4.Redis 支持数据的备份, 即 master-slave 模式的数据备份。
5.Redis的所有操作都是原子性的,同时Redis还支持对几个操作合并后的原子性执行。(事务)
6.性能极高 – Redis 能读的速度是 110000 次/s,写的速度是 81000 次/s

2.RDB 和 AOF 机制

RDB:Redis DataBase
Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的。这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。我们默认的就是RDB,一般情况下不需要修改这个配置!
有时候在生产环境我们会将这个文件进行备份!

rdb保存的文件是dump.rdb 都是在我们的配置文件中快照中进行配置的!
RDB触发机制

  • 1、save的规则满足的情况下,会自动触发rdb规则
  • 2、执行 flushall 命令,也会触发我们的rdb规则!
    3、退出redis,也会产生 rdb 文件!

优点:
1、整个Redis数据库将只包含一个文件 dump.rdb,方便持久化。
2、容灾性好,方便备份。
3、性能最大化,fork 子进程来完成写操作,让主进程继续处理命令,所以是 IO 最大化。使用单独子进
程来进行持久化,主进程不会进行任何 IO 操作,保证了 redis 的高性能
4.相对于数据集大时,比 AOF 的启动效率更高。(适合大规模数据恢复)
缺点:
1、数据安全性低。RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢
失。所以这种方式更适合数据要求不严谨的时候)
2、由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导
致整个服务器停止服务几百毫秒,甚至是1秒钟。

AOF:Append Only File
以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以
打开文件看到详细的操作记录
快照功能(RDB)并不是非常耐久(durable): 如果 Redis因为某些原因而造成故障停机,那么服务器将丢失最近写入、以及未保存到快照中的那些数据。 从 1.1 版本开始, Redis 增加了一种完全耐久的持久化方式: AOF 持久化。

优点:
1、数据安全,Redis中提供了3中同步策略,即每秒同步、每修改同步和不同步。事实上,每秒同步也
是异步完成的,其效率也是非常高的,所差的是一旦系统出现宕机现象,那么这一秒钟之内修改的数据
将会丢失。而每修改同步,我们可以将其视为同步持久化,即每次发生的数据变化都会被立即记录到磁
盘中。。
2、通过 append 模式写文件,即使中途服务器宕机也不会破坏已经存在的内容,可以通过 redischeck-aof 工具解决数据一致性问题。
3、AOF 机制的 rewrite 模式。定期对AOF文件进行重写,以达到压缩的目的
缺点:
1、AOF 文件比 RDB 文件大,且恢复速度慢。
2、数据集大的时候,比 rdb 启动效率低。
3、运行效率没有RDB高

AOF文件比RDB更新频率高,优先使用AOF还原数据。
AOF比RDB更安全也更大
RDB性能比AOF好
如果两个都配了优先加载AOF

3.Redis单线程快的原因

1)纯内存操作
2)核心是基于非阻塞的IO多路复用机制
3)单线程反而避免了多线程的频繁上下文切换带来的性能问题

4.Redis主从复制

主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(Master/Leader),后者称为从节点(Slave/Follower), 数据的复制是单向的!只能由主节点复制到从节点(主节点以写为主、从节点以读为主)。

默认情况下,每台Redis服务器都是主节点,一个主节点可以有0个或者多个从节点,但每个从节点只能由一个主节点。
作用:

  • 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余的方式。
  • 故障恢复:当主节点故障时,从节点可以暂时替代主节点提供服务,是一种服务冗余的方式
  • 负载均衡:在主从复制的基础上,配合读写分离,由主节点进行写操作,从节点进行读操作,分担服务器的负载;尤其是在读多写少的场景下,通过多个从节点分担负载,提高并发量。
  • 高可用基石:主从复制还是哨兵和集群能够实施的基础。

为什么使用集群

  • 单台服务器难以负载大量的请求
  • 单台服务器故障率高,系统崩坏概率大
  • 单台服务器内存容量有限。

5.什么是哨兵模式

主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑 哨兵模式。
在这里插入图片描述

这里哨兵模式作用:

  • 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
  • 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。

然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。
各个哨兵之间还会进行监控,这样就形成了多哨兵模式。
在这里插入图片描述
哨兵模式优缺点
优点:

  • 哨兵集群,基于主从复制模式,所有主从复制的优点,它都有
  • 主从可以切换,故障可以转移,系统的可用性更好
  • 哨兵模式是主从模式的升级,手动到自动,更加健壮

缺点:

  • Redis不好在线扩容,集群容量一旦达到上限,在线扩容就十分麻烦
  • 实现哨兵模式的配置其实是很麻烦的,里面有很多配置项

6.缓存穿透与雪崩

服务的高可用问题
Redis缓存的使用,极大的提升了应用程序的性能和效率,特别是数据查询方面。但同时,它也带来了一些问题。其中,最要害的问题,就是数据的一致性问题(事务在运行时不能保证原子性),从严格意义上讲,这个问题无解。如果对数据的一致性要求很高,那么就不能使用缓存。
另外的一些典型问题就是,缓存穿透、缓存雪崩和缓存击穿。目前,业界也都有比较流行的解决方案。

一.缓存穿透
缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中(秒杀!),于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。洪水攻击。数据库也查不到就没有缓存,就会一直与数据库访问。

在这里插入图片描述

解决方案

1.布隆过滤器:
对所有可能查询的参数以Hash的形式存储,以便快速确定是否存在这个值,在控制层先进行拦截校验,校验不通过直接打回,减轻了存储系统的压力。
2.缓存空对象
一次请求若在缓存和数据库中都没找到,就在缓存中方一个空对象用于处理后续这个请求。

二.缓存击穿(量太大 缓存过期)
相较于缓存穿透,缓存击穿的目的性更强,一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。这就是缓存被击穿,只是针对其中某个key的缓存不可用而导致击穿,但是其他的key依然可以使用缓存响应。

解决方案

1.设置热点数据永不过期
这样就不会出现热点数据过期的情况,但是当Redis内存空间满的时候也会清理部分数据,而且此种方案会占用空间,一旦热点数据多了起来,就会占用部分空间。
2.加互斥锁(分布式锁)
在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。保证同时刻只有一个线程访问。这样对锁的要求就十分高。

三.缓存雪崩
大量的key设置了相同的过期时间,导致在缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。
在这里插入图片描述
解决方案

  • redis高可用
    这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群
  • 限流降级
    这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
  • 数据预热
    数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。

7.Redis持久化数据和缓存怎么做扩容?

  • 如果Redis被当做缓存使用,使用一致性哈希实现动态扩容缩容。
  • 如果Redis被当做一个持久化存储使用,必须使用固定的keys-to-nodes映射关系,节点的数量一旦确定不能变化。否则的话(即Redis节点需要动态变化的情况),必须使用可以在运行时进行数据
    再平衡的一套系统,而当前只有Redis集群可以做到这样。

8.Redis的内存用完了会发生什么?

如果达到设置的上限,Redis的写命令会返回错误信息(但是读命令还可以正常返回。)或者你可以配置内存淘汰机制,当Redis达到内存上限时会冲刷掉旧的内容。

9.Redis如何做内存优化?

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值