重点面试题

1线程池的七大参数以及他们之间是怎么工作的?


1.corePoolSize(核心线程数):创建线程池后不会立即创建核心线程,当有任务到达时才触发核心线程的创建;核心线程会一直存活,即使没有任务需要执行;当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理。(设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时摧毁)
2.maximumPoolSize(最大线程数):当线程数>=corePoolSize且任务队列已满时线程池会创建新线程来处理任务;当线程数=maximumPoolSize,且任务队列已满时,线程池会根据配置的拒绝策略处理任务。
3.keepAliveTime(多余的空闲线程的存活时间):当线程空闲时间达到keepAliveTime时,线程会摧毁,直到线程数量=corePoolSize。(如果allowCoreThreadTimeout=true,则会直到线程数量=0)
4.unit(keepAliveTime的计量单位)
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒
5.workQueue (任务队列):新任务被提交且核心线程数满后,会进入到此工作队列中,任务调度从队列中取出任务
6.threadFactory(创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon线程等)
7.handler (拒绝策略):所有拒绝策略实现RejectedExecutionHandler接口,读者可根据实际情况扩展该接口实现自己的拒绝策略 。
handler的四大拒绝策略:当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize时,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 (线程池中默认的拒绝策略。)
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 默默丢弃
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的等待时间最久的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务,谁调用返回给谁

2.ArrayList和LinkedList的区别是什么?


● ArrayList基于动态数组实现的非线程安全的集合;LinkedList基于链表实现的非线程安全的集合。
● 对于随机index访问的get和set方法,一般ArrayList的速度要优于LinkedList。因为ArrayList直接通过数组下标直接找到元素;LinkedList要移动指针遍历每个元素直到找到为止。
● 新增和删除元素,一般LinkedList的速度要优于ArrayList。因为ArrayList在新增和删除元素时,可能扩容和复制数组;LinkedList实例化对象需要时间外,只需要修改指针即可。
● LinkedList集合不支持 高效的随机随机访问(RandomAccess)
● ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间

3.HashMap的存储结构?

面试官高频 - 图2

HashMap初始size为16
HashMap 的负载因子 默认 0.75,也就是会浪费 1/4 的空间,比如说当前的容器容量是16,负载因子是0.75,16*0.75=12,也就是说,当容量达到了12的时候就会进行扩容操作,扩容必须为原来的两倍,扩容因子为0.75是因为空间利用率比较高,而且避免了相当多的Hash冲突,使得底层的链表或者是红黑树的高度比较低,提升了空间效率。
JDK1.7的时候使用的是数组+单链表的数据结构,用单链表进行的纵向延伸,当采用头插法就是能够提高插入的效率,但是也会容易出现逆序且环形链表死循环问题。但是在JDK1.8之后 使用的是数组+链表+红黑树的数据结构,红黑树使用的是尾插法,能够避免出现逆序且链表死循环的问题。(当链表的深度达到8的时候,也就是默认阈值,就会自动扩容把链表转成红黑树的数据结构来把时间复杂度从O (n)变成O(nlogN)提高了效率),当深度小于6的时候红黑树又会转换为链表。

扩容后数据存储位置的计算方式也不一样:1.在JDK1.7的时候是直接用hash值和需要扩容的二进制数进行&(这里就是为什么扩容的时候为啥一定必须是2的多少次幂的原因所在,因为如果只有2的n次幂的情况时最后一位二进制数才一定是1,这样能最大程度减少hash碰撞) (hash值 & length-1)2、而在JDK1.8的时候直接用了JDK1.7的时候计算的规律,也就是扩容前的原始位置+扩容的大小值=JDK1.8的计算方式,而不再是JDK1.7的那种异或的方法。但是这种方式就相当于只需要判断Hash值的新增参与运算的位是0还是1就直接迅速计算出了扩容后的储存方式。
在计算hash值的时候,JDK1.7用了9次扰动处理=4次位运算+5次异或,而JDK1.8只用了2次扰动处理=1次位运算+1次异或。

4.HashMap和HashTable的区别?线程安全方面有什么不一样的?


怎么储存元素的
先计算key的hash值 根据这个值再二次hash然后对数组的长度取模 对应数组的下标、
如果下标位置没有元素则直接创建node存入数组中
如果有 就会先进行equal比较 如果相同则取代该元素 不同的话则判断链表高度插入链表
数组扩容
头插法尾插法
链表头插法的会颠倒原来一个散列桶里面链表的顺序。在并发的时候原来的顺序被另外一个线程a颠倒了,而被挂起线程b恢复后拿扩容前的节点和顺序继续完成第一次循环后,又遵循a线程扩容后的链表顺序重新排列链表中的顺序,最终形成了环。
尾插法的好处:
使用头插会改变链表的上的顺序,但是如果使用尾插,在扩容时会保持链表元素原本的顺序,就不会出现链表成环的问题了。
Java7在多线程操作HashMap时可能引起死循环,原因是扩容转移后前后链表顺序倒置,在转移过程中修改了原来链表中节点的引用关系。
Java8在同样的前提下并不会引起死循环,原因是扩容转移后前后链表顺序不变,保持之前节点的引用关系。
虽然jdk8后HashMap在多线程情况下扩容时不会出现死循环的情况,但是在多线程情况下仍不推荐使用HashMap。因为HashMap中的put/get方法并未加锁,这意味着无法保证上一秒put的值,下一秒取到的是原值,所以线程安全无法保证。多线程下推荐使用ConcurrentHashMap。

(1)同步性(线程安全与不安全)
hashtable源码中所有的方法都加了synchronized 无论有多少线程进来都需要排队,线程就会很安全,并且具有同步性,但是效率会非常低
hashmap中方法就没有
(2) 继承的父类不同
Hashtable 继承父类dictionary
Hashmap 继承abstractmap
(3) 对null值支持不同
Hashtable key 与value都不可以为null,Hashtable的put方法中有 如果value为null则会抛出空指针异常
Hashmap key 与value 都可以为null 若果key为null 会以0作为Hash值计算。
(4) 初始化方式以及哈希值算法不同 扩容方法不同,寻址方式也不同
Hashtable的数组默认大小是11,在构造方法的时候就构造了数组,加载因子0.75f,初始化是在调用构造方法的时候初始化,每次扩容是原来的2n+1倍,哈希值直接通过hashcode获取 寻址方式直接去掉哈希值符号位然后对数组长度取模
Hashmap 初始容量为16,加载因子为0.75 初始化是在第一次调用put方法时候进行初始化,每次扩容是原来容量的2倍 哈希值通过hashcode获取哈希值之后,高十六位与低16位进行异或操作获得最终哈希值(防止分布不均匀) 寻址方式:哈希值与数组长度-1的值相与(直接计算出下标位置)

5.HashMap什么时候扩容,如果考虑到线程安全的情况你会用什么?用volatile


hashmap中有一个参数叫做负载因子,代表当hashmap容量使用率达到一定比率时触发扩容。1.8版本后hashmap由数组加链表和红黑树组成。数组的每个元素是一个链表或者红黑树的根节点。对于数组元素下是链表还是红黑树是由对应数据结构深度决定,当链表深度超过8会转换成红黑树,当红黑树深度小于6会退化成链表。其实这样的转换是为了提高查询效率。那么其实对于负载因子的计算就是判断数组中有多少元素被使用。当使用比率超过负载因子就会触发扩容。
jdk8中是当插入的数据个数大于容量*负载因子时就会扩容。/ 桶中已经使用的个数大于桶的装载因子时。

6.volatile的理解?


volatile 关键字的两层语义
一旦一个共享变量(类的成员变量、类的静态成员变量)被 volatile 修饰之后,那么就具备
了两层语义:
1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这
新值对其他线程来说是立即可见的。
2)禁止进行指令重排序。
用 volatile 修饰之后,变量的操作:
第一:使用 volatile 关键字会强制将修改的值立即写入主存;
第二:使用 volatile 关键字的话,当线程 2 进行修改时,会导致线程 1 的工作内存中缓存变
量 stop 的缓存行无效(反映到硬件层的话,就是 CPU 的 L1 或者 L2 缓存中对应的缓存行无
效);
第三:由于线程 1 的工作内存中缓存变量 stop 的缓存行无效,所以线程 1 再次读取变量 stop
的值时会去主存读取

7.乐观锁和悲观锁


当程序中可能出现并发的情况时,就需要保证在并发情况下数据的准确性,以此确保当前用户和其他用户一起操作时,所得到的结果和他单独操作时的结果是一样的。这就叫做并发控制。并发控制的目的是保证一个用户的工作不会对另一个用户的工作产生不合理的影响。
没有做好并发控制,就可能导致脏读、幻读和不可重复读等问题。
悲观锁:Synchronzied 和 ReetrantLock
当要对数据库中的一条数据进行修改的时候,为了避免同时被其他人修改,最好的办法就是直接对该数据进行加锁以防止并发。这种借助数据库锁机制,在修改数据之前先锁定,再修改的方式被称之为悲观并发控制【Pessimistic Concurrency Control,缩写“PCC”,又名“悲观锁”】。
悲观锁,具有强烈的独占和排他特性。它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度。因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。
之所以叫做悲观锁,是因为这是一种对数据的修改持有悲观态度的并发控制方式。总是假设最坏的情况,每次读取数据的时候都默认其他线程会更改数据,因此需要进行加锁操作,当其他线程想要访问数据时,都需要阻塞挂起。
悲观锁的实现:

  1. 传统的关系型数据库使用这种锁机制,比如行锁、表锁、读锁、写锁等,都是在操作之前先上锁。
  2. Java 里面的同步 synchronized 关键字的实现。
    2️⃣悲观锁主要分为共享锁和排他锁:
    ● 共享锁【shared locks】又称为读锁,简称 S 锁。顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。
    ● 排他锁【exclusive locks】又称为写锁,简称 X 锁。顾名思义,排他锁就是不能与其他锁并存,如果一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁。获取排他锁的事务可以对数据行读取和修改。
    悲观并发控制实际上是“先取锁再访问”的保守策略,为数据处理的安全提供了保证。但是在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会。另外还会降低并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理那行数据。
    乐观锁:
    乐观锁是相对悲观锁而言的,乐观锁假设数据一般情况不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果冲突,则返回给用户异常信息,让用户决定如何去做。乐观锁适用于读多写少的场景,这样可以提高程序的吞吐量。
    乐观锁采取了更加宽松的加锁机制。也是为了避免数据库幻读、业务处理时间过长等原因引起数据处理错误的一种机制,但乐观锁不会刻意使用数据库本身的锁机制,而是依据数据本身来保证数据的正确性。
    乐观锁的实现:
  3. CAS 实现:Java 中java.util.concurrent.atomic包下面的原子变量使用了乐观锁的一种 CAS 实现方式。
  4. 版本号控制:一般是在数据表中加上一个数据版本号 version 字段,表示数据被修改的次数。当数据被修改时,version 值会 +1。当线程 A 要更新数据时,在读取数据的同时也会读取 version 值,在提交更新时,若刚才读取到的 version 值与当前数据库中的 version 值相等时才更新,否则重试更新操作,直到更新成功。
    2️⃣说明
    乐观并发控制相信事务之间的数据竞争(data race)的概率是比较小的,因此尽可能直接做下去,直到提交的时候才去锁定,所以不会产生任何锁和死锁。

8.JVM的内存模型?每个区域做什么?

面试官高频 - 图3
jdk1.8之前的内存模型,其中方法区和堆是是线程共享的,但是在jdk1.8之后
元数据区取代了永久代。元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元数据空间并不在虚拟机中,而是使用本地内存
程序计数器
它是一块较小的内存空间,可以看做是指向当前线程所执行的方法的行号指示器,目的是 比如当前线程正在执行,有一个优先级比较高的线程需要执行,那么当前线程就被挂起,cpu时间片被抢占过去执行另一个线程,当另一个线程执行完了之后回到当前线程根据行号指示器找当前线程到刚刚挂起时的位置继续执行。
Java虚拟机栈
java虚拟机栈与程序计数器一样,也是线程私有的,他的生命周期和线程保持一致。他是存储当前线程运行方法时所需要的数据、指令、返回地址。在每个方法执行时,虚拟机栈都会创建一个栈帧(Stack Frame),用于存储:局部变量表(存变量)、操作数栈(一块临时内存空间 用于变量操作比如 a+b)、动态链接(根据动态链接可以快速找到在方法区中的位置)、方法出口等信息。虚拟机栈中假如有无数个局部变量,其中有很多是对象类型的,那么对象是存在堆中的,而虚拟机栈中存的仅仅是堆中对象的内存地址(指针引用)。虚拟机栈和堆是引用关系。
方法区
类经过类装载子系统会被加载到方法区
方法区和堆一样,是各个线程共享的区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码数据等。方法区中如果有很多个静态变量,其中有静态变量是对象类型的,那么对象是存在堆中的,而方法区中存的仅仅是堆中对象的内存地址(指针引用)。方法区和堆也是引用关系。
本地方法栈
● 本地方法栈底层存储的是虚拟机使用到C++的Native方法。当有线程调用本地方法的时候,会在本地方法栈开辟一块儿内存空间到线程的栈帧中。

java堆是java虚拟机管理的内存中最大的一块,java堆是被所有线程共享的一块内存区域,堆的唯一目的就是存放实例对象,几乎所有的对象实例都在这里分配内存。如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。


堆内存

1. 新生代

是用来存放新生的对象。一般占据堆的 1/3 空间。由于频繁创建对象,所以新生代会频繁触发
MinorGC 进行垃圾回收。新生代又分为 Eden 区、 ServivorFrom、 ServivorTo 三个区。
1. Eden 区
Java 新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老
年代)。当 Eden 区内存不够的时候就会触发 MinorGC,对新生代区进行
一次垃圾回收。
2. ServivorFrom
上一次 GC 的幸存者,会被存入到servivor区。
3. ServivorTo
保留了前两个区 MinorGC 过程中的幸存者。
4. MinorGC 的过程(复制->清空->互换)
MinorGC 采用复制算法:
1: eden、 servicorFrom 复制到 ServicorTo,年龄+1首先,把 Eden 和 ServivorFrom 区域中存活的对象复制到 ServicorTo 区域(如果有对象的年龄以及达到了老年的标准,则赋值到老年代区),同时把这些对象的年龄+1(如果 ServicorTo 不够位置了就放到老年区);
2: 清空 eden、 servicorFrom然后,清空 Eden 和 ServicorFrom 中的对象;
3: ServicorTo 和 ServicorFrom 互换最后, ServicorTo 和 ServicorFrom 互换,原 ServicorTo 成为下一次 GC 时的 ServicorFrom区

2. 老年代

主要存放应用程序中生命周期长的内存对象。
老年代的对象比较稳定,所以 ManorGC 不会频繁执行。在进行 ManorGC 前一般都先进行了一次 ManorGC ,使得有新生代的对象晋身入老年代,导致空间不够用时才触发。当无法找到足够大的连续空间分配给新创建的较大对象时也会提前触发一次 ManorGC 进行垃圾回收腾出空间。
ManorGC 采用标记清除算法:首先扫描一次所有老年代,标记出存活的对象,然后回收没有标记的对象。 ManorGC 的耗时比较长,因为要扫描再回收。ManorGC 会产生内存碎片,为了减少内存损耗,我们一般需要进行合并或者标记出来方便下次直接分配。当老年代也满了装不下的时候,就会抛出 OOM(Out of Memory)异常。

9.索引的分类?


1、单值索引:即一个索引只包含单个列,一个表可以有多个单列索引。
2、唯一索引:索引的值必须唯一,但允许有空值。
3、复合索引:一个索引包含多个列。

10.Mysql索引的结构?


索引是在Mysql的存储引擎中实现的,而不是在服务器层实现的。所以每种存储引擎的索引都不一定完全相同,也不是所有的存储引擎都支持所有的索引类型的。Mysql目前提供了4种索引:
1、BTree索引:最常见的索引类型,大部分索引都支持B树索引。
2、Hash索引:只有Memory引擎支持,使用场景简单。
3、R-Tree索引(空间索引):空间索引是MylSAM引擎的一个特殊索引类型,主要用于地理空间数据索引,通常使用较少。只支持MyISAM引擎。
4、Full-text(全文索引):全文索引也是MylSAM的一个特殊索引类型,主要用于全文索引,支持MyISAM引擎,InnoDB从Mysql5.6版本开始支持全文索引。

我们平常所说的索引,如果没有特别的指明,都是指B+树(多路平衡搜索树,并不一定是二叉树)的结构组织的索引。其中聚簇索引、复合索引、前缀索引、唯一索引默认都是使用B+树索引,统称为索引。

11.你用过什么中间件?

12.RabbitMQ你了解多少?


● RabbitMQ就是一种消息队列的实现,可以简单理解为生产者/消费者模式,生产商将生产的商品放在商店(消息队列),消费者有需求就来商店买,商店(消息队列)实现两者间的异步和解耦。
● RabbitMQ是实现AMQP(高级消息队列协议)的消息中间件的一种,RabbitMQ主要是为了实现系统之间的双向解耦而实现的。当生产者大量产生数据时,消费者无法快速消费,那么需要一个中间层。保存这个数据。
● AMQP,即Advanced Message Queuing Protocol,高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在,反之亦然。AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。
● RabbitMQ是一个开源的AMQP实现,服务器端用Erlang语言编写,支持多种客户端。

13.Gateway网关适合处理什么?

微服务框架当前大行其道,网关在微服务架构中是一个非常重要的部分,网关一般作为项目的统一请求入口提供给前端开发人员,前端开发人员不用知道每个微服务的请求地址。网关可以统一对所有请求做过滤、限流、负载均衡、监控等处理,而不必在每个微服务项目重复处理请求。网关配合注册中心也可以很好的实现微服务的动态扩容,只需要在网关将请求路由转发到注册中心的微服务上即可,由注册中心进行负载均衡处理。gateway是非阻塞的,并且是spring官方开发的,所以拥有强劲的性能。

14.创建线程有几种方式?


(1)继承Thread抽象,重写其的run方法,在程序的运行接口中,创建这个继承Thread抽象类的对象,使用start方法开启线程。
(2)实现Runnable接口,通过实现Runnable接口,在程序运行的主接口中创建相对应的对象,然后创建Thread对象,使用thread对象调用start()方法来开启线程。
(3)使用Callable和Future创建线程
上面的两种方式都有这两个问题:

  1. 无法获取子线程的返回值
  2. run方法不可以抛出异常
    为了解决这两个问题,我们就需要用到Callable这个接口了。说到接口,上面的Runnable接口实现类实例是作为Thread类的构造函数的参数传入的,之后通过Thread的start执行run方法中的内容。但是Callable并不是Runnable的子接口,是个全新的接口,它的实例不能直接传入给Thread构造,所以需要另一个接口来转换一下。
    (4)线程池创建线程

15.wait和sleep的区别?


1、sleep是Thread的方法,但是wait是Object中的方法。
2、sleep方法不会释放lock,但是wait会释放,而且会加入到等待队列中。
3、sleep方法不依赖于同步器synchronized,可以在任何地方使用,但是wait需要依赖synchronized关键字(写在同步代码块中)。
4、sleep不需要被唤醒(休眠之后推出阻塞),但是wait需要(不指定时间需要被别人中断)。
5、sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常

16.堆内存和栈内存的区别?


通常我们定义一个基本数据类型的变量,一个对象的引用,还有就是函数调用的现场保存都
使用 JVM 中的栈空间;而通过 new 关键字和构造器创建的对象则放在堆空间,堆是垃圾收
集器管理的主要区域,由于现在的垃圾收集器都采用分代收集算法,所以堆空间还可以细分
为新生代和老生代,再具体一点可以分为 Eden、Survivor(又可分为 From Survivor 和 To
Survivor)、Tenured;方法区和堆都是各个线程共享的内存区域,用于存储已经被 JVM 加载
的类信息、常量、静态变量、JIT 编译器编译后的代码等数据;程序中的字面量(
literal)如
直接书写的 100、”hello”和常量都是放在常量池中,常量池是方法区的一部分,。栈空间操
作起来最快但是栈很小,通常大量的对象都是放在堆空间,栈和堆的大小都可以通过 JVM
的启动参数来进行调整,栈空间用光了会引发 StackOverflowError,而堆和常量池空间不足
则会引发 OutOfMemoryError。

17.垃圾回收算法有哪些?简述其原理


1、标记-清除算法:
该算法先标记,后清除,将所有需要回收的内存进行标记,然后清除;这种算法的缺点是:效率比较低;标记清除后会出现大量不连续的内存碎片,这些碎片太多可能会使存储大对象会触发GC回收,造成内存浪费以及时间的消耗。
2、复制算法:
复制算法将可用的内存分成两份,每次使用其中一块,当这块回收之后把未回收的复制到另一块内存中,然后把使用的清除。这种算法运行简单,解决了标记-清除算法的碎片问题,但是这种算法代价过高,需要将可用内存缩小一半,对象存活率较高时,需要持续的复制工作,效率比较低。
3、标记-整理算法:
标记整理算法是针对复制算法在对象存活率较高时持续复制导致效率较低的缺点进行改进的,该算法是在标记-清除算法基础上,不直接清理,而是使存活对象往一端游走,然后清除一端边界以外的内存,这样既可以避免不连续空间出现,还可以避免对象存活率较高时的持续复制。这种算法适合老生代。
4、分代搜集算法:
分代收集算法就是目前虚拟机使用的回收算法,将内存分为各个年代,在不同年代使用不同的算法,从而使用最合适的算法,新生代存活率低,可以使用复制算法。而老年代对象存活率高,没有额外空间对它进行分配担保,所以使用标记整理算法。

18.聊聊SpringIOC? IOC是怎么自动装配的?


IOC不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IOC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。
其实IOC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IOC/DI思想中,应用程序就变成被动的了,被动的等待IOC容器来创建并注入它所需要的资源了。
IOC很好的体现了面向对象设计法则之一—— 好莱坞法则:“别找我们,我们找你”;即由IOC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。

我这是村的人家的面试题 太多了 我一看都不会
。。。我上个班同学去杭州的面试题大集合在一块了 看来的看看了

19.SpringIOC常用的注解有哪些?


@Component 用于把当前类对象存入Spring容器中。
@Autowired 自动按照类型注入。只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功。
@Qualifier 在按照类中注入的基础之上再按照名称注入。它在给类成员注入时不能单独使用(可以跟Autowired搭配之用)。但是在给方法参数注入时可以。
@Resource 直接按照bean的id注入。它可以独立使用。
@Scope 用于指定bean的作用范围。

20.工作中用到的设计模式有哪些?

单例模式

21.如何实现单例模式?


2.1. 懒汉式
该方式是使用synchronized关键字进行加锁,保证了线程安全性。
优点:在第一次调用才初始化,避免了内存浪费。
缺点:对获取实例方法加锁,大大降低了并发效率。
由于加了锁,对性能影响较大,不推荐使用。
2.2 饿汉式
饿汉式是利用类加载机制来避免了多线程的同步问题,所以是线程安全的。
优点:未加锁,执行效率高。
缺点:类加载时就初始化实例,造成内存浪费。
如果对内存要求不高的情况,还是比较推荐使用这种方式。

22.数据库多表联查的时候连接方式有几种?


连接查询主要分为三种:内连接、外连接、交叉连接。
内连接inner join
左连接left join 右连接rightjoin

23.mysql表增加字段的sql语句怎么写?


ALTER TABLE USERS ADD alias varchar(20) COMMENT ‘别名’;

24.Sql优化


● 最大化利用索引;
● 尽可能避免全表扫描;
● 减少无效数据的查询;
详解 https://zhuanlan.zhihu.com/p/299051996

25.索引这块了解吗?


索引的分类:
1、单值索引:即一个索引只包含单个列,一个表可以有多个单列索引。
2、唯一索引:索引的值必须唯一,但允许有空值。
3、复合索引:一个索引包含多个列。
索引的生效和失效情况:
1、复合索引中如果有范围查询 那么只有这个范围查询字段之前的列的索引生效,这个范围查询后面的列的索引将失效。
2、如果在索引列上进行运算操作(比如字符串截取),那么索引也将失效。
3、如果是字符串不加单引号,也会造成索引失效。虽然不加单引号也能查询出来数据,但是Mysql底层的优化器检测到字段类型是是一个字符串类型,那么它将会对这个值进行隐式类型转换,转换之后索引就失效了,因为他的底层是对字段进行了运算操作。
4、like 以%开头,索引无效;当like前缀没有%,后缀有%时,索引有效。
5、or语句前后没有同时使用索引。当or左右查询字段只有一个是索引,该索引失效,只有当or左右查询字段均为索引时,才会生效

26.Linux的常用命令

创建目录 mkdir [-p] 目录名
删除非空目录或包含内容的目录
# 只能删除空目录
rmdir 目录名
# 删除非空目录
rm [-rf] 目录名
# -r:代表递归删除目录下的全部内容
# -f:不询问,直接删除
复制目录cp-r 来源目录 目标目录
移动、重命名目录
移动、重命名目录,会根据第二个参数指定具体操作逻辑
mv 目录名 新目录名 | 路径
解压压缩包
tar [-zxvf] 压缩包名称 [-C 路径]
# -z: 代表压缩包后缀是.gz的
# -x: 代表解压
# -v: 解压时,打印详细信息
# -f: -f选项必须放在所有选项的最后,代表指定文件名称
# -C 路径: 代表将压缩包内容解压到指定路径
打包成压缩包
tar [-zcvf] 压缩包名称 文件1 文件2 目录1 目录2 …
# -c: 代表打包
# 其他参数同上

27.MySQl的执行流程是什么

面试官高频 - 图4
连接器
主要负责用户登录数据库,进行用户的身份认证,包括校验账户密码,权限等操作,如果用户账户密码已通过,连接器会到权限表中查询该用户的所有权限,之后在这个连接里的权限逻辑判断都是会依赖此时读取到的权限数据,也就是说,后续只要这个连接不断开,即时管理员修改了该用户的权限,该用户也是不受影响的。
查询缓存
连接建立后,执行查询语句的时候,会先查询缓存,Mysql会先校验这个sql是否执行过,以Key-Value的形式缓存在内存中,Key是查询预计,Value是结果集。如果缓存key被命中,就会直接返回给客户端,如果没有命中,就会执行后续的操作,完成后也会把结果缓存起来,方便下一次调用。当然在真正执行缓存查询的时候还是会校验用户的权限,是否有该表的查询条件。
Mysql 查询不建议使用缓存,因为对于经常更新的数据来说,缓存的有效时间太短了,往往带来的效果并不好,对于不经常更新的数据来说,使用缓存还是可以的,Mysql 8.0 版本后删除了缓存的功能,官方也是认为该功能在实际的应用场景比较少,所以干脆直接删掉了。
分析器
mysql 没有命中缓存,那么就会进入分析器,分析器主要是用来分析SQL语句是来干嘛的,分析器也会分为几步:
第一步,词法分析,一条SQL语句有多个字符串组成,首先要提取关键字,比如select,提出查询的表,提出字段名,提出查询条件等等。做完这些操作后,就会进入第二步。
第二步,语法分析,主要就是判断你输入的sql是否正确,是否符合mysql的语法。
完成这2步之后,mysql就准备开始执行了,但是如何执行,怎么执行是最好的结果呢?这个时候就需要优化器上场了。
优化器
优化器的作用就是它认为的最优的执行方案去执行(虽然有时候也不是最优),比如多个索引的时候该如何选择索引,多表查询的时候如何选择关联顺序等。
执行器
当选择了执行方案后,mysql就准备开始执行了,首先执行前会校验该用户有没有权限,如果没有权限,就会返回错误信息,如果有权限,就会去调用引擎的接口,返回接口执行的结果。

28.多线程方法notify和notifyAll?


notify()方法不能唤醒某个具体的线程,所以只有一个线程在等 待的时候它才有用武之地。
而 notifyAll()唤醒所有线程并允许他们争夺锁确保了至少有一个线程能继续运行。

29.分布式事务和单机式事务有什么区别?

30.和equals有什么区别?


== 等于比较运算符,如果进行比较的两个操作数都是数值类型,即使他们的数据类型不相同,只要他们的值相等,也都将返回true.如果两个操作数都是引用类型,那么只有当两个引用变量的类型具有父子关系时才可以比较,而且这两个引用必须指向同一个对象,才会返回true.(在这里我们可以理解成比较的是两个变量的内存地址)
equals()方法是Object类的方法,在Object类中的equals()方法体内实际上返回的就是使用进行比较的结果。
== 是java提供的等于比较运算符,用来比较两个变量指向的内存地址是否相同.而equals()是Object提供的一个方法.Object中equals()方法的默认实现就是返回两个对象的比较结果.但是equals()可以被重写,所以我们在具体使用的时候需要关注equals()方法有没有被重写.
赋值方式中如果调用了new关键字,一定会在内存中给你分配一个新的地址
给Integer类型赋值的时候,如果没有调用new关键字,并且值在-128与+127之间,包括-128和+127,那么指向的都是同一个内存位置.
Integer类中重写了equals()方法,使用equals()方法进行比较的时候,实际上比较的内存中最终指向的值的内存位置,不是直接比较变量的内存位置.
重写的equals()方法

31.你有了解过回表查询吗?


32.Redis如何进行持久化的?


Redis提供2种方式实现持久化:1.RDB 2.AOF
同时开启RDB和AOF的注意事项:
如果同时开启了AOF和RDB持久化,那么在Redis宕机重启之后,需要加载一个持久化文件,优先选择AOF文件。
如果先开启了RDB,再次开启AOF,如果RDB执行了持久化,那么RDB文件中的内容会被AOF覆盖掉。
6.1 RDB
RDB是Redis默认的持久化机制
RDB持久化文件,速度比较快,而且存储的是一个二进制的文件,传输起来很方便。
RDB持久化的时机:
save 900 1:在900秒内,有1个key改变了,就执行RDB持久化。
save 300 10:在300秒内,有10个key改变了,就执行RDB持久化。
save 60 10000:在60秒内,有10000个key改变了,就执行RDB持久化。
RDB无法保证数据的绝对安全。
RDB存储的是数据,时间间隔无法实时持久化
6.2 AOF
AOF持久化机制默认是关闭的,Redis官方推荐同时开启RDB和AOF持久化,更安全,避免数据丢失。
AOF持久化的速度,相对RDB较慢的,存储的是一个文本文件,到了后期文件会比较大,传输困难。
AOF持久化时机。
appendfsync always:每执行一个写操作,立即持久化到AOF文件中,性能比较低。
appendfsync everysec:每秒执行一次持久化。
appendfsync no:会根据你的操作系统不同,环境的不同,在一定时间内执行一次持久化。
AOF相对RDB更安全,推荐同时开启AOF和RDB。
AOF存储的是命令(更改数据的命令),可以实时持久化

33.redis的淘汰机制


在Redis内存已经满的时候,添加了一个新的数据,执行淘汰机制。
volatile-lru:在内存不足时,Redis会在设置过了生存时间的key中干掉一个最近最少使用的key。
allkeys-lru:在内存不足时,Redis会在全部的key中干掉一个最近最少使用的key。
volatile-lfu:在内存不足时,Redis会在设置过了生存时间的key中干掉一个最近最少频次使用的key。
allkeys-lfu:在内存不足时,Redis会再全部的key中干掉一个最近最少频次使用的key。
volatile-random:在内存不足时,Redis会再设置过了生存时间的key中随机干掉一个。
allkeys-random:在内存不足时,Redis会再全部的key中随机干掉一个。
volatile-ttl:在内存不足时,Redis会在设置过了生存时间的key中干掉一个剩余生存时间最少的key。
noeviction:(默认)在内存不足时,直接报错。
指定淘汰机制的方式:maxmemory-policy 具体策略,设置Redis的最大内存:maxmemory 字节大小

34.redis的数据类型


常用的5种数据结构:
● key-string:一个key对应一个值。
● key-hash:一个key对应一个Map。
● key-list:一个key对应一个List列表。
● key-set:一个key对应一个Set集合。
● key-zset:一个key对应一个有序的Set集合。等价于:Map
另外3种数据结构:
● HyperLogLog:计算近似值的。
● GEO:地理位置。
● BIT:一般存储的也是一个字符串,存储的是一个byte[]。

33.分布式锁你有了解过吗?什么场景使用?

34.你用过什么消息队列?

● RabbitMQ就是一种消息队列的实现,可以简单理解为生产者/消费者模式,生产商将生产的商品放在商店(消息队列),消费者有需求就来商店买,商店(消息队列)实现两者间的异步和解耦。当生产者大量产生数据时,消费者无法快速消费,那么需要一个中间层。保存这个数据。
1.异步通信
有些业务不想也不需要立即处理消息。消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。想向队列中放入多少消息就放多少,然后在需要的时候再去处理它们。
2.解耦
降低工程间的强依赖程度,针对异构系统进行适配。在项目启动之初来预测将来项目会碰到什么需求,是极其困难的。通过消息系统在处理过程中间插入了一个隐含的、基于数据的接口层,两边的处理过程都要实现这一接口,当应用发生变化时,可以独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束。
3.冗余
有些情况下,处理数据的过程会失败。除非数据被持久化,否则将造成丢失。消息队列把数据进行持久化直到它们已经被完全处理,通过这一方式规避了数据丢失风险。许多消息队列所采用的”插入-获取-删除”范式中,在把一个消息从队列中删除之前,需要你的处理系统明确的指出该消息已经被处理完毕,从而确保你的数据被安全的保存直到你使用完毕。
4.扩展性
因为消息队列解耦了你的处理过程,所以增大消息入队和处理的频率是很容易的,只要另外增加处理过程即可。不需要改变代码、不需要调节参数。便于分布式扩容。
5.过载保护
在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量无法提取预知;如果以为了能处理这类瞬间峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用消息队列能够使关键组件顶住突发的访问压力,而不会因为突发的超负荷的请求而完全崩溃。
6.可恢复性
系统的一部分组件失效时,不会影响到整个系统。消息队列降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。
7.顺序保证
在大多使用场景下,数据处理的顺序都很重要。大部分消息队列本来就是排序的,并且能保证数据会按照特定的顺序来处理。
8.缓冲
在任何重要的系统中,都会有需要不同的处理时间的元素。消息队列通过一个缓冲层来帮助任务最高效率的执行,该缓冲有助于控制和优化数据流经过系统的速度。以调节系统响应时间。
9.数据流处理
分布式系统产生的海量数据流,如:业务日志、监控数据、用户行为等,针对这些数据流进行实时或批量采集汇总,然后进行大数据分析是当前互联网的必备技术,通过消息队列完成此类数据收集是最好的选择。

35.消息的幂等性怎么保障?怎么解决这个情况?

幂等性:无论一个消息,接收多少次,也不会出现重复消费的问题比如:支付,用户手速过快,导致快速的多次的发起支付请求,但是只能有一次进行。
MQ有可能会出现一个消息发送多次,导致消息的重复性
什么情况下,会出现消息的重复?
1.网络抖动
2.网络闪断
3.端点异常 服务端或者发送端或者消费端
消息出现非幂等性,就会导致消息的重复
解决方案:
1.唯一ID+指纹码
唯一ID 可以使用:雪花算法等等 这种分布式唯一ID生成器
指纹码:就是一段密文,加密规则各不相同
常用:时间戳+随机码+唯一ID+业务ID 采用一定加密技术 进行密文生成
2.基于Redis的原子性实现
Redis的原子性,自增啥的都可以,关键Redis支持集群


36.雪花算法了解过吗?
37.锁 Synchronized和ReentrantLock


1、ReentrantLock 拥有Synchronized相同的并发性和内存语义,此外还多了 锁投票,定时锁等候和中断锁等候
ReentrantLock获取锁定与三种方式:
a) lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁
b) tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;
c)tryLock(long timeout,TimeUnit unit), 如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;
d) lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断
2、synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中
3、在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态;

38.锁的状态

39.Spring中bean的生命周期

面试官高频 - 图5
————————————初始化————————————

  • BeanNameAware.setBeanName() 在创建此bean的bean工厂中设置bean的名称,在普通属性设置之后调用,在InitializinngBean.afterPropertiesSet()方法之前调用
  • BeanClassLoaderAware.setBeanClassLoader(): 在普通属性设置之后,InitializingBean.afterPropertiesSet()之前调用
  • BeanFactoryAware.setBeanFactory() : 回调提供了自己的bean实例工厂,在普通属性设置之后,在InitializingBean.afterPropertiesSet()或者自定义初始化方法之前调用
  • EnvironmentAware.setEnvironment(): 设置environment在组件使用时调用
  • EmbeddedValueResolverAware.setEmbeddedValueResolver(): 设置StringValueResolver 用来解决嵌入式的值域问题
  • ResourceLoaderAware.setResourceLoader(): 在普通bean对象之后调用,在afterPropertiesSet 或者自定义的init-method 之前调用,在 ApplicationContextAware 之前调用。
  • ApplicationEventPublisherAware.setApplicationEventPublisher(): 在普通bean属性之后调用,在初始化调用afterPropertiesSet 或者自定义初始化方法之前调用。在 ApplicationContextAware 之前调用。
  • MessageSourceAware.setMessageSource(): 在普通bean属性之后调用,在初始化调用afterPropertiesSet 或者自定义初始化方法之前调用,在 ApplicationContextAware 之前调用。
  • ApplicationContextAware.setApplicationContext(): 在普通Bean对象生成之后调用,在InitializingBean.afterPropertiesSet之前调用或者用户自定义初始化方法之前。在ResourceLoaderAware.setResourceLoader,ApplicationEventPublisherAware.setApplicationEventPublisher,MessageSourceAware之后调用。
  • ServletContextAware.setServletContext(): 运行时设置ServletContext,在普通bean初始化后调用,在InitializingBean.afterPropertiesSet之前调用,在 ApplicationContextAware 之后调用注:是在WebApplicationContext 运行时
  • BeanPostProcessor.postProcessBeforeInitialization() : 将此BeanPostProcessor 应用于给定的新bean实例 在任何bean初始化回调方法(像是InitializingBean.afterPropertiesSet或者自定义的初始化方法)之前调用。这个bean将要准备填充属性的值。返回的bean示例可能被普通对象包装,默认实现返回是一个bean。
  • BeanPostProcessor.postProcessAfterInitialization() : 将此BeanPostProcessor 应用于给定的新bean实例 在任何bean初始化回调方法(像是InitializingBean.afterPropertiesSet或者自定义的初始化方法)之后调用。这个bean将要准备填充属性的值。返回的bean示例可能被普通对象包装
  • InitializingBean.afterPropertiesSet(): 被BeanFactory在设置所有bean属性之后调用(并且满足BeanFactory 和 ApplicationContextAware)。

    40.SpringMVC的执行流程


    面试重点:springmvc运行原理图 (戏称 一个中心DispatcherServlet三个基本点【处理器映射器,处理器适配器,视图解析器】)
    1、浏览器发送请求 /hello 给前端控制器DispatcherServlet ,这个是一个大脑,控制所有其他组件的运行
    2、前端控制器去找处理器映射器(编写了一堆某个url对应某个类中的哪个方法),处理器映射器告诉前端控制器,哪个类中的哪个方法可以处理
    3、前端控制器去找处理器适配器,告诉它执行哪个类中的哪个方法,处理器适配器接到命令之后,就去执行处理器(Handler),其实就是一个方法,也就是说一个方法就是一个处理器。处理器执行完方法之后,将一个ModelAndView对象给处理器适配器,处理器适配器上交处理结果—ModelAndView(数据+页面)
    4、前端控制器拿着ModelAndView 对象传递给视图解析器,让其解析,解析的结果传递给前端控制器
    5、前端控制器拿到渲染之后的页面,传递给浏览器。
    关注点:前端控制器,处理器映射器,处理器适配器,视图解析器,都不需要我们来管,自动运行的,我们开发人员只需要关注处理器如何编写,页面如何编写即可。

41.Mysql数据库引擎


MyISAM:基于ISAM (Indexed Sequential Access Method索引顺序访问方法),它是存储记录和文件的标准方法,不是事务安全的,不支持外键,支持表锁,如果有大量的select,MyISAM比较合适。MyISAM表示独立于操作系统之外的,通俗点说就是你可以很轻松的将MylSAM表从windows移植到linux或者从linux移植到windows。

InnoDB:支持事务安全,支持外键、支持行锁,事务是它的最大优点,如果有大量的insert和update,建议用lnnoDB,特使是针对高并发和QPS (query per second)较高的情况。show engines

43.内存淘汰策略


44.CAS和ABA


CAS(CompareAndSwap),顾名思义,CAS就是比较和交换。
如果多个线程CAS更新同一个变量,只有一个可以成功,其他全部失败。失败的线程可以再次尝试更新。
CAS 即比较并交换。是解决多线程并行情况下使用锁造成性能损耗的一种机制,CAS 操作包含三个操作数——内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值(V)与预期原值(A)相匹配,那么处理器会自动将该位置值更新为新值(B)。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。CAS 有效地说明了“我认为位置(V)应该包含值(A)。如果包含该值,则将新值(B)放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可”。Java 中,sun.misc.Unsafe 类提供了硬件级别的原子操作来实现这个 CAS。java.util.concurrent包下大量的类都使用了这个 Unsafe.java 类的 CAS 操作。
当多个线程尝试使用 CAS 同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。

CAS看起来很爽,但是会导致“ABA问题”。
CAS算法实现一个重要前提需要取出内存中某时刻的数据,而在下时刻比较并替换,那么在这个时间差类会导致数据的变化。
比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。如果链表的头在变化了两次后恢复了原值,但是不代表链表就没有变化。因此前面提到的原子操作AtomicStampedReference/AtomicMarkableReference就很有用了。这允许一对变化的元素进行原子操作。
解决方案:
Java中提供了两个类来解决这个问题。
● AtomicStampedReference
● AtomicMarkableReference
在原有类的基础上,除了比较与修改期待的值外,增加了一个时间戳。对时间戳也进行CAS操作。这也称为双重CAS。从上例中看到。每次修改一个结点,其时间戳都发生变化。这样即使共享一个复用结点,最终CAS也能返回正常的结果。

45.Bean的作用域


● 应用scope配置项配置Bean的作用域
● 应用单例模式singleton
● 应用原型模式prototype
1、作用域scope配置项
作用域限定了Spring Bean的作用范围,在Spring配置文件定义Bean时,通过声明scope配置项,可以灵活定义Bean的作用范围。例如,当你希望每次IOC容器返回的Bean是同一个实例时,可以设置scope为singleton;当你希望每次IOC容器返回的Bean实例是一个新的实例时,可以设置scope为prototype。
scope配置项有5个属性,用于描述不同的作用域。
① singleton :单例,默认的作用域,使用 singleton 定义的 bean 将只有一个实例。
② prototype :多例,每次通过容器中的 getBean 方法获取 prototype 定义的 beans 时,都
会产生一个新的 bean 的实例。
③ request :对于每次 Http 请求,使用 request 定义的 bean 都会产生一个新的实例,只有在
web 应用时候,该作用域才会有效。
④ session :会话,对于每次 Http Session,使用 session 定义的 Bean 都将产生一个新的实例。
⑤ global-session :全局会话,所有会话共享一个实例。每个全局的 Http Sesisonn,使用 session 定义的本都将产生一个新的实例。

46.SpringBoot的启动流程

46.springboot自动装配原理

面试官高频 - 图6

SpringBoot项目无需各种配置文件,一个main方法,就能把项目启动起来。那么我们看看SpringBoot是如何进行自动配置和启动的。
SpringBoot通过main方法启动SpringApplication类的静态方法run()来启动项目。
面试官高频 - 图7
根据注释的意思,run方法从一个使用了默认配置的指定资源启动一个SpringApplication并返回ApplicationContext对象,这个默认配置如何指定呢?
面试官高频 - 图8
这个默认配置来源于@SpringBootApplication注解,这个注解是个复合注解,里面还包含了其他注解。
面试官高频 - 图9其中有三个注解是比较重要的:
1:@SpringBootConfiguration:这个注解的底层是一个@Configuration注解,意思被@Configuration注解 修饰的类是一个IOC容器,支持JavaConfig的方式来进行配置;
2:@ComponentScan:这个就是扫描注解的意思,默认扫描当前类所在的包及其子包下包含的注解,将 @Controller/@Service/@Component/@Repository等注解加载到IOC容器中;
3:@EnableAutoConfiguration:这个注解表明启动自动装配,里面包含连个比较重要的注解 @AutoConfigurationPackage和@Import。
面试官高频 - 图10
1:@AutoConfigurationPackage和@ComponentScan一样,也是将主配置类所在的包及其子包里面的组件扫描到IOC容器中,但是区别是@AutoConfigurationPackage扫描@Enitity、@MapperScan等第三方依赖的注解,@ComponentScan只扫描@Controller/@Service/@Component/@Repository这些常见注解。所以这两个注解扫描的对象是不一样的。
2:@Import(AutoConfigurationImportSelector.class)是自动装配的核心注解,AutoConfigurationImportSelector.class里面又实现了ImportSelector接口,重写了selectImports方法
面试官高频 - 图11
selectImports方法还调用了getCandidateConfigurations方法
面试官高频 - 图12
getCandidateConfigurations方法中,我们可以看下断言,说找不到META-INF/spring.factories,由此可见,这个方法是用来找META-INF/spring.factories文件的
面试官高频 - 图13
我们可以定位到这个方法所在的类处于spring-boot-autoconfigure-.jar包中,
面试官高频 - 图14
_其中spring.factories文件是一组组的key=value的形式,包含了key为EnableAutoConfiguration的全类名,value是一个_AutoConfiguration类名的列表,以逗号分隔。
面试官高频 - 图15
最终,@EnableAutoConfiguration注解通过@SpringBootApplication注解被间接的标记在了SpringBoot的启动类上,SpringApplicaton.run方法的内部就会执行selectImports方法,进而找到所有JavaConfig配置类全限定名对应的class,然后将所有自动配置类加载到IOC容器中。
那么这些类是如何获取默认属性值的呢?以ServletWebServerFactoryAutoConfiguration为例,它是Servlet容器的自动配置类
面试官高频 - 图16
该类上开启了@EnableConfigurationProperties(ServerProperties.class)注解,最终找到了ServerProperties类。至此,我们大致可以了解。在全局配置的属性如:server.port等,通过@ConfigurationProperties注解,绑定到对应的XxxxProperties配置实体类上封装为一个bean,然后再通过@EnableConfigurationProperties注解导入到Spring容器中。
面试官高频 - 图17
总结:SpringBoot启动的时候通过@EnableAutoConfiguration注解找到META-INF/spring.factories文件中的所有自动配置类,并对其加载,这些自动配置类都是以AutoConfiguration结尾来命名的。它实际上就是一个JavaConfig形式的IOC容器配置类,通过以Properties结尾命名的类中取得在全局配置文件中配置的属性,如server.port。
Properties类的含义:封装配置文件的相关属性。
AutoConfiguration类的含义:自动配置类,添加到IOC容器中。

47.一级缓存和二级缓存和懒加载机制

1 一级缓存
sqlSession对象 默认的就是一级缓存
session级别的缓存
一级缓存是SqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个数据结构为HashMap的内存区域用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。
一级缓存的作用域是同一个SqlSession,在同一个sqlSession中两次执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存区域)中,第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。当一个sqlSession结束后该sqlSession中的一级缓存也就不存在了。Mybatis默认开启一级缓存。
如果没有启动事务,mybatis的一级缓存在spring中是没有作用的.
2 二级缓存
mapper级别的缓存
二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession去操作数据库得到数据会存在二级缓存区域,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace,不同的sqlSession两次执行相同namespace下的sql语句且向sql中传递参数也相同即最终执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。Mybatis默认没有开启二级缓存需要在setting全局参数中配置开启二级缓存。
注意:相关实体类需要实现Serializable接口,否则关闭session时候报错

48.数据库存储过程


MySQL 5.0 版本开始支持存储过程。
存储过程(Stored Procedure)是一种在数据库中存储复杂程序,以便外部程序调用的一种数据库对象。
存储过程是为了完成特定功能的SQL语句集,经编译创建并保存在数据库中,用户可通过指定存储过程的名字并给定参数(需要时)来调用执行。
存储过程思想上很简单,就是数据库 SQL 语言层面的代码封装与重用。
优点
● 存储过程可封装,并隐藏复杂的商业逻辑。
● 存储过程可以回传值,并可以接受参数。
● 存储过程无法使用 SELECT 指令来运行,因为它是子程序,与查看表,数据表或用户定义函数不同。
● 存储过程可以用在数据检验,强制实行商业逻辑等。
缺点
● 存储过程,往往定制化于特定的数据库上,因为支持的编程语言不同。当切换到其他厂商的数据库系统时,需要重写原有的存储过程。
● 存储过程的性能调校与撰写,受限于各种数据库系统。

49.数据库索引什么时候失效

1、复合索引中如果有范围查询 那么只有这个范围查询字段之前的列的索引生效,这个范围查询后面的列的索引将失效。
面试官高频 - 图18
2、如果在索引列上进行运算操作(比如字符串截取),那么索引也将失效。
面试官高频 - 图19
3、如果是字符串不加单引号,也会造成索引失效。虽然不加单引号也能查询出来数据,但是Mysql底层的优化器检测到字段类型是是一个字符串类型,那么它将会对这个值进行隐式类型转换,转换之后索引就失效了,因为他的底层是对字段进行了运算操作。
面试官高频 - 图20
4、like 以%开头,索引无效;当like前缀没有%,后缀有%时,索引有效。
面试官高频 - 图21
5、or语句前后没有同时使用索引。当or左右查询字段只有一个是索引,该索引失效,只有当or左右查询字段均为索引时,才会生效
面试官高频 - 图22

50.springcloud的组件

注册中心(配置中心):Nacos(阿里)
Nacos核心的2大作用:1.作为注册中心使用,可以实现服务的发现和注册功能。2.配置中心,实现微服务的配置统一管理.
服务调用OpenFeign是一种声明式的web 客户端,可以使用它的注解创建接口,它也支持自定义编解码。可以实现服务的远程调用
OpenFeign有两个主要注解: @EnableFeignClients 用于开启feign功能,@FeignClient 用于定义feign 接口.。
网关中心Gateway 网关可以统一对所有请求做过滤、限流、负载均衡、监控等处理,而不必在每个微服务项目重复处理请求。网关配合注册中心也可以很好的实现微服务的动态扩容,只需要在网关将请求路由转发到注册中心的微服务上即可,由注册中心进行负载均衡处理。gateway是非阻塞的,并且是spring官方开发的,所以拥有强劲的性能。
熔断降级Sentinel(阿里) sentinel熔断器,主要的作用就是2个:1.流量控制 2.熔断降级
流量控制:根据测试的服务可处理请求上限,设置接口的请求上限,防止服务器宕机
熔断降级:防止核心接口异常,导致服务雪崩。服务一旦不可用(超时、数据库、网络抖动、死锁、服务资源(CPU 内存)等等),可以立即执行降级方法
链路跟踪Sleuth 服务请求的日志系统,可以记录一次请求的完整过程中的时间,
Zipkin 监控服务调用链路

51.es倒排索引

52.聚合索引和非聚合索引的区别


聚簇索引(聚集索引)
聚簇索引就是按照每张表的主键构造一颗B+树,同时叶子节点中存放的就是整张表的行记录数据,也将聚集索引的叶子节点称为数据页。这个特性决定了索引组织表中数据也是索引的一部分,每张表只能拥有一个聚簇索引。
Innodb通过主键聚集数据,如果没有定义主键,innodb会选择非空的唯一索引代替。如果没有这样的索引,innodb会隐式的定义一个主键来作为聚簇索引。
辅助索引(非聚簇索引)
在聚簇索引之上创建的索引称之为辅助索引,辅助索引访问数据总是需要二次查找。辅助索引叶子节点存储的不再是行的物理位置,而是主键值。通过辅助索引首先找到的是主键值,再通过主键值找到数据行的数据页,再通过数据页中的Page Directory找到数据行。
Innodb辅助索引的叶子节点并不包含行记录的全部数据,叶子节点除了包含键值外,还包含了相应行数据的聚簇索引键。
辅助索引的存在不影响数据在聚簇索引中的组织,所以一张表可以有多个辅助索引。在innodb中有时也称辅助索引为二级索引。

53.怎么排查死锁

什么是死锁? 假设我们有一把蓝钥匙,可以打开一扇蓝色的门;以及一把红钥匙,可以打开一扇红色的门。两把钥匙被保存在一个皮箱里。同时我们定义六种行为:获取蓝钥匙,打开蓝色门,归还蓝钥匙,获取红钥匙,打开红色门,归还红钥匙。
面试官高频 - 图23
同时开始运行线程A和线程B
面试官高频 - 图24
可以看到,当两个线程都运行到第三步的时候,线程A在等线程B归还红钥匙,线程B在等线程A归还蓝钥匙,因而两个线程都永远卡在那里无法前进。这就是形成了死锁。
排查死锁的三种方式:
1、使用 jps + jstack:在jdk中或windons命令窗口,使用 jps -l
面试官高频 - 图25
检查到死锁
面试官高频 - 图26

54.openFeign远程通信底层的运行过程

55.nginx负载均衡反向代理