基础面试题大纲
线程池
为什么要使用线程池
应用场景
它用在当对象的初始化过程代价较大或者使用频率较高时,比如线程池,数据库连接池等。运用对象池化技术可以显著地提升性能。
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语句优化
为了提高查询效率,优先原则是避免全表扫描(在
where
子句的列以及order by
涉及的列建立索引)不要使用
select *
这样的语句,要用具体的列名代替*
尽量避免在
where
子句中使用!= 、< 、>
操作符号尽量避免在
where
子句中使用is null
或者is not null
,否则索引会失效尽量避免在
where
子句中使用or
,否则索引会失效尽量避免在
where
子句中使用in
或者not in
,否则索引会失效尽量避免在
where
子句中对字段进行函数操作,否则会放弃索引进行全表扫描。多表查询使用
JOIN
代替子查询explain 获取查询语句的执行计划 查看type字段类型
常用的类型有:
all < index(需要优化) < range< ref< eq_ref< const< system
(从左到右,性能从差到好)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的生命周期
Spring中的bean的生命周期主要包含四个阶段:实例化Bean --> Bean属性填充 --> 初始化Bean -->销毁Bean
首先是实例化Bean,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚末初始化的依赖时,容器就会调用doCreateBean()方法进行实例化,实际上就是通过反射的方式创建出一个bean对象
Bean实例创建出来后,接着就是给这个Bean对象进行属性填充,也就是注入这个Bean依赖的其它bean对象
属性填充完成后,进行初始化Bean操作,初始化阶段又可以分为几个步骤:
- 执行Aware接口的方法 Spring会检测该对象是否实现了xxxAware接口,通过Aware类型的接口,可以让我们拿到Spring容器的资源。如实现BeanNameAware接口可以获取到BeanName,实现BeanFactoryAware接口可以获取到工厂对象BeanFactory等
- 执行BeanPostProcessor的前置处理方法postProcessBeforelnitialization(),对Bean进行一些自定义的前置处理
- 判断Bean是否实现了InitializingBean接口,如果实现了,将会执行lnitializingBean的afeterPropertiesSet()初始化方法;
- 执行用户自定义的初始化方法,如init-method等;
- 执行BeanPostProcessor的后置处理方法postProcessAfterinitialization()
初始化完成后,Bean就成功创建了,之后就可以使用这个Bean, 当Bean不再需要时,会进行销毁操作,
- 首先判断Bean是否实现了DestructionAwareBeanPostProcessor接口,如果实现了,则会执行DestructionAwareBeanPostProcessor后置处理器的销毁回调方法
- 其次会判断Bean是否实现了DisposableBean接口,如果实现了将会调用其实现的destroy()方法
- 最后判断这个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 的执行流程如下:
- 客户端发送请求至前端控制器(DispatcherServlet)
- 前端控制器根据请求路径,进入对应的处理器
- 处理器调用相应的业务方法
- 处理器获取到相应的业务数据
- 处理器把组装好的数据交还给前端控制器
- 前端控制器将获取的 ModelAndView 对象传给视图解析器(ViewResolver)
- 前端控制器获取到解析好的页面数据
- 前端控制器将解析好的页面返回给客户端
Mybatis
# $的区别
- #对传入的数据视为字符串,会进行预编译。如:select from user where name = #{name},比如我传一个csdn,那么传过来就是 select from user where name = ‘csdn’.
- $将传入的数据直接显示生成在sql中。如:order by $user_id$,如果传入的值是111,那么解析成sql时的值为order by user_id, 如果传入的值是id,则解析成的sql为order by id.
- #方式能够很大程度防止sql注入,$方式无法防止Sql注入。
- $方式一般用于传入数据库对象,例如传入表名.
- 一般能用#的就别用$.
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注解与全局配置文件中对应的属性进行绑定的。