跳到主要内容

基础面试题大纲

线程池

为什么要使用线程池

应用场景

它用在当对象的初始化过程代价较大或者使用频率较高时,比如线程池,数据库连接池等。运用对象池化技术可以显著地提升性能。

 7大核心参数

  1.核心线程数(Core Pool Size): 线程池中最小的线程数,即在线程池中一直保持的线程数量,不受空闲时间的影响。

  2.最大线程数(最大池大小)

  3.空闲线程存活时间(Keep Alive Time):当线程池中的线程数超过核心线程数时,多余的线程会被回收,此参数即为非核心线程的空闲时间,超过此时间将被回收。

  4.工作队列(Work Queue): 用于存储等待执行的任务的队列,当线程池中的线程数达到核心线程数时,新的任务将被加入工作队列等待执行。

  5.拒绝策略(Reject Execution Handler):当线程池和工作队列都已经达到最大容量,无法再接收新的任务时,拒绝策略将被触发。常见的拒绝策略有抛出异常、直接丢弃任务、丢弃队列中最老的任务等。

  6.线程工厂 (Thread Factory): 用于创建新的线程,可定制线程名字、线程组、优先级等。

  7.阻塞策略(Block Policy): 当工作队列已满时,向线程池中添加任务的策略。常见的策略有:直接抛出异常、阻塞调用者、丢弃任务等。

流程

HashMap1.7和1.8的区别

答:HashMap 在 JDK 7 和 JDK 8 的主要区别如下。

  • 存储结构:JDK 7 使用的是数组 + 链表;JDK 8 使用的是数组 + 链表 + 红黑树。
  • 存放数据的规则:JDK 7 无冲突时,存放数组;冲突时,存放链表;JDK 8 在没有冲突的情况下直接存放数组,有冲突时,当链表长度小于 8 时,存放在单链表结构中,当链表长度大于 8 时,树化并存放至红黑树的数据结构中。
  • 插入数据的方式:JDK 7 使用的是头插法(先将原位置的数据移到后 1 位,再插入数据到该位置);JDK 8 使用的是尾插法(直接插入到链表尾部/红黑树)。

SQL优化

概要

MySQL性能优化的一个很重要的手段就是对SQL语句的优化。其中最重要的方式就是使用索引。

5 SQL语句优化

  1. 为了提高查询效率,优先原则是避免全表扫描(在where子句的列以及order by涉及的列建立索引)

  2. 不要使用select * 这样的语句,要用具体的列名代替*

  3. 尽量避免在where子句中使用 != 、< 、>操作符号

  4. 尽量避免在where子句中使用 is null 或者 is not null,否则索引会失效

  5. 尽量避免在where子句中使用or,否则索引会失效

  6. 尽量避免在where子句中使用in 或者 not in ,否则索引会失效

  7. 尽量避免在where 子句中对字段进行函数操作,否则会放弃索引进行全表扫描。

  8. 多表查询使用 JOIN 代替子查询

  9. explain 获取查询语句的执行计划 查看type字段类型

    常用的类型有: all < index(需要优化) < range< ref< eq_ref< const< system(从左到右,性能从差到好)

  10. Profiling:可以用来准确定位一条SQL的性能瓶颈

Spring

loC

控制反转(Inversion of Control,IoC),顾名思义所谓的控制反转就是把创建对象的权利交给框架去控制,而不需要人为地去创建,这样就实现了可插拔式的接口编程,有效地降低代码的耦合度,降低了扩展和维护的成本。

DI

依赖注入(Dependency Injection,DI),是组件之间依赖关系由容器在运行期决定,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

IoC 和 DI 的关系

IoC 是 Spring 中一个极为重要的概念,而 DI 则是实现 IoC 的方法和手段。

AOP

是什么:面向切面编程,它就好比将系统按照功能分类,每一个类别就是一个“切面”,我们再针对不同的切面制定相应的规则,(类似开发模式被)称为面向切面编程。

Spring AOP 实现原理

基于动态代理模式:JDK动态代理和Cglib动态代理.如果代理对象为接口则使用JDK动态代理模式,如果代理对象为非接口则使用Cglib动态代理模式

AOP 作用

  • 在不修改源代码的及调用方式的情况下对方法增强
  • 提高代码的可重用性,提高开发效率,并且便于维护

AOP 使用场景

  • 事务
  • 缓存
  • 权限
  • 日志系统
  • 全局异常处理
  • 性能统计
  • 分布式锁
  • 安全统一效验

MySQL事务

是什么:事务是一系列的数据库操作,是数据库应用的基本单位。

在 MySQL 中只有 InnoDB 引擎支持事务,它的四个特性如下:

  • 原子性(Atomic),事务是一个不可分割的工作逻辑单元
  • 一致性(Consistency),事务完成时,所有的数据必须是一致的,要么一起成功要么一起失败
  • 隔离性(Isolation),多个并发事务之间是相互隔离的
  • 持久性(Durability),事务提交后,其结果永久保存在数据库中。

spring 事务

Spring 事务隔离级别有哪些?

答:Spring 事务的隔离级包含以下五种:

  • ISOLATION_DEFAULT:用底层数据库的设置隔离级别,数据库设置的是什么我就用什么;
  • ISOLATION_READ_UNCOMMITTED:未提交读,最低隔离级别、事务未提交前,就可被其他事务读取(会出现幻读、脏读、不可重复读);
  • ISOLATION_READ_COMMITTED:提交读,一个事务提交后才能被其他事务读取到(会出现幻读、不可重复读),Orache、SQL server 的默认级别;
  • ISOLATION_REPEATABLE_READ:可重复读,保证多次读取同一个数据时,其值都和事务开始时候的内容是一致,禁止读取到别的事务未提交的数据(会造成幻读),MySQL 的默认级别;
  • ISOLATION_SERIALIZABLE:序列化,代价最高最可靠的隔离级别,该隔离级别能防止脏读、不可重复读、幻读。

默认值为 ISOLATION_DEFAULT 遵循数据库的事务隔离级别设置。

脏读(读取未提交数据)

A事务读取B事务尚未提交的数据,此时如果B事务发生错误并执行回滚操作,那么A事务读取到的数据就是脏数据。就好像原本的数据比较干净、纯粹,此时由于B事务更改了它,这个数据变得不再纯粹。这个时候A事务立即读取了这个脏数据,但事务B良心发现,又用回滚把数据恢复成原来干净、纯粹的样子,而事务A却什么都不知道,最终结果就是事务A读取了此次的脏数据,称为脏读。

不可重复读(前后多次读取,数据内容不一致)

事务A在执行读取操作,由整个事务A比较大,前后读取同一条数据需要经历很长的时间 。而在事务A第一次读取数据,比如此时读取了小明的年龄为20岁,事务B执行更改操作,将小明的年龄更改为30岁,此时事务A第二次读取到小明的年龄时,发现其年龄是30岁,和之前的数据不一样了,也就是数据不重复了,系统不可以读取到重复的数据,成为不可重复读。

幻读(前后多次读取,数据总量不一致)

事务A在执行读取操作,需要两次统计数据的总量,前一次查询数据总量后,此时事务B执行了新增数据的操作并提交后,这个时候事务A读取的数据总量和之前统计的不一样,就像产生了幻觉一样,平白无故的多了几条数据,成为幻读。

不可重复读和幻读到底有什么区别呢?

(1) 不可重复读是读取了其他事务更改的数据,针对update操作

解决:使用行级锁,锁定该行,事务A多次读取操作完成后才释放该锁,这个时候才允许其他事务更改刚才的数据。

(2) 幻读是读取了其他事务新增的数据,针对insert和delete操作

解决:使用表级锁,锁定整张表,事务A多次读取数据总量之后才释放该锁,这个时候才允许其他事务新增数据。

这时候再理解事务隔离级别就简单多了呢。

Spring 声明式事务无效可能的原因有哪些?

答:可能的原因如下:

  • MySQL 使用的是 MyISAM 引擎,而 MyISAM 是不支持事务的;
  • @Transactional 使用在非 public 方法上,@Transactional 注解只能支持 public 级别,其他类型声明的事务不会生效;
  • @Transactional 在同一个类中无事务方法 A() 内部调用有事务方法 B(),那么此时 B() 事物不会生效。

spring 事务传播机制

是什么: 指一个类的方法调用了另一个类的方法将事务传递给他,事务的传播机制主要针对调用者而言.

七种传播机制:

  • PROPAGATION_REQUIRED, spring默认的事务传播机制;如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中
  • PROPAGATION_SUPPORTS, 支持事务,当前存在事务,则加入当前事务,如果当前没有事务,就以非事务方法执行
  • PROPAGATION_MANDATORY, 必须有事务,当前存在事务,则加入当前事务,如果当前事务不存在,则抛出异常
  • PROPAGATION_REQUIRES_NEW, 新建事务,创建一个新事务,如果存在当前事务,则挂起该事务
  • PROPAGATION_NOT_SUPPORTED, 不支持事务,如果当前存在事务,则挂起当前事务;如果不存在事务,以非事务方式执行
  • PROPAGATION_NEVER, 不能有事务: 存在事务就抛出异常;不存在事务,就以非事务方式执行
  • PROPAGATION_NESTED, 内嵌事务: 如果当前事务存在,则在嵌套事务中执行,否则和REQUIRED的操作一样

Bean的生命周期

  1. Spring中的bean的生命周期主要包含四个阶段:实例化Bean --> Bean属性填充 --> 初始化Bean -->销毁Bean

  2. 首先是实例化Bean,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚末初始化的依赖时,容器就会调用doCreateBean()方法进行实例化,实际上就是通过反射的方式创建出一个bean对象

  3. Bean实例创建出来后,接着就是给这个Bean对象进行属性填充,也就是注入这个Bean依赖的其它bean对象

  4. 属性填充完成后,进行初始化Bean操作,初始化阶段又可以分为几个步骤:

    1. 执行Aware接口的方法 Spring会检测该对象是否实现了xxxAware接口,通过Aware类型的接口,可以让我们拿到Spring容器的资源。如实现BeanNameAware接口可以获取到BeanName,实现BeanFactoryAware接口可以获取到工厂对象BeanFactory等
    2. 执行BeanPostProcessor的前置处理方法postProcessBeforelnitialization(),对Bean进行一些自定义的前置处理
    3. 判断Bean是否实现了InitializingBean接口,如果实现了,将会执行lnitializingBean的afeterPropertiesSet()初始化方法;
    4. 执行用户自定义的初始化方法,如init-method等;
    5. 执行BeanPostProcessor的后置处理方法postProcessAfterinitialization()
  5. 初始化完成后,Bean就成功创建了,之后就可以使用这个Bean, 当Bean不再需要时,会进行销毁操作,

    1. 首先判断Bean是否实现了DestructionAwareBeanPostProcessor接口,如果实现了,则会执行DestructionAwareBeanPostProcessor后置处理器的销毁回调方法
    2. 其次会判断Bean是否实现了DisposableBean接口,如果实现了将会调用其实现的destroy()方法
    3. 最后判断这个Bean是否配置了dlestroy-method等自定义的销毁方法,如果有的话,则会自动调用其配置的销毁方法

Spring的三级缓存解决循环依赖

循环依赖 三级缓存 提前暴露

循环依赖是什么:A依赖B,B依赖A

1、A创建过程中需要B,于是先将A放到三级缓存,去实例化B。

2、B实例化的过程中发现需要A,于是B先查一级缓存寻找A,如果没有,再查二级缓存,如果还没有,再查三级缓存,找到了A,然后把三级缓存里面的这个A放到二级缓存里面,并删除三级缓存里面的A。

3、B顺利初始化完毕,将自己放到一级缓存里面(此时B里面的A依然是创建中的状态)。然后回来接着创建A,此时B已经创建结束,可以直接从一级缓存里面拿到B,去完成A的创建,并将A放到一级缓存。

Spring MVC

DispatcherServlet执行流程

Spring MVC 的执行流程如下:

  1. 客户端发送请求至前端控制器(DispatcherServlet)
  2. 前端控制器根据请求路径,进入对应的处理器
  3. 处理器调用相应的业务方法
  4. 处理器获取到相应的业务数据
  5. 处理器把组装好的数据交还给前端控制器
  6. 前端控制器将获取的 ModelAndView 对象传给视图解析器(ViewResolver)
  7. 前端控制器获取到解析好的页面数据
  8. 前端控制器将解析好的页面返回给客户端

Mybatis

# $的区别

  1. #对传入的数据视为字符串,会进行预编译。如:select from user where name = #{name},比如我传一个csdn,那么传过来就是 select from user where name = ‘csdn’.
  2. $将传入的数据直接显示生成在sql中。如:order by $user_id$,如果传入的值是111,那么解析成sql时的值为order by user_id, 如果传入的值是id,则解析成的sql为order by id.
  3. #方式能够很大程度防止sql注入,$方式无法防止Sql注入。
  4. $方式一般用于传入数据库对象,例如传入表名.
  5. 一般能用#的就别用$.

SpringBoot 自动装配的过程

注意

Spring Boot 2.7以后从META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports配置文件中获取所有自动装配的类

Spring Boot启动的时候会通过@EnableAutoConfiguration注解找到META-INF/spring.factories配置文件中的所有自动配置类,并对其进行加载,而这些自动配置类都是以AutoConfiguration结尾来命名的,它实际上就是一个JavaConfig形式的Spring容器配置类,它能通过以Properties结尾命名的类中取得在全局配置文件中配置的属性,而XxxxProperties类是通过@ConfigurationProperties注解与全局配置文件中对应的属性进行绑定的。