前言:上一期的面试题感觉阅读量尚可 我这边再补充一部分 如果觉得有帮助的话 欢迎点一个免费的关注和收藏点赞谢谢!
请简述一下Spring Bean的生命周期?
- 实例化阶段->构造函数调用
- 属性注入阶段->依赖注入
- 初始化阶段->实现初始化方法
- 使用阶段->正常业务逻辑调用
- 销毁阶段->实现销毁方法
请说一下Spring中包含哪些设计模式?
- 工厂模式
- 具体应用场景分布于Spring的
BeanFactory
和ApplicationContext
他们负责创建和管理各种Bean
对象;
- 具体应用场景分布于Spring的
- 单例模式
- 在
Spring
中,默认情况的Bean
是单例的,通过单例模式来确保一个类只有一个实例,并提供一个全局访问点;
- 在
- 代理模式:
Spring
的AOP
中广泛使用了代理模式,通过代理对象,可以在不修改目标对象代码的情况下来添加额外功能;
- 装饰器模式:
- 在
Spring
中,某些功能的增强可以通过装饰器模式实现。比如,对输入流或输出流的包装,或者对某些服务的功能扩展;
- 在
- 观察者模式:
- Spring 的事件机制采用了观察者模式。当一个特定的事件发生时,多个观察者可以收到通知并进行相应的处理;
如何解决瞬时大流量高并发?
在解决瞬时大流量高并发时,我们可以从前端+后端+架构+监控四个方面进行处理:
- 前端部分:
- 设置合理的缓存时间,让浏览器进行静态资源缓存,减少对服务器的重复请求;
- 利用
CDN
缓存静态资源,将资源分发到离用户更近的节点,提高访问速度; - 对于非关键的资源,可以采用异步加载的方式,不影响页面的主要内容加载;
- 后端部分:
- 使用负载均衡器将流量均匀地分发到多个服务器上,避免单个服务器过载;
- 使用
Redis
等工具缓存热点数据,减少数据库访问压力; - 合理设置数据库的缓存参数,提高数据库的查询性能;
- 为经常查询的字段创建合适的索引,提高查询速度。但要注意避免过度索引,以免影响写入性能;
- 当数据量过大时,可以将数据库进行分库分表,分散数据存储和查询压力;
- 优化
SQL
语句,避免复杂的查询和全表扫描; - 对于一些耗时的操作,可以采用消息队列异步处理的方式,不影响主流程的响应时间。
- 限制系统的并发访问量,当流量超过一定阈值时,拒绝部分请求,保护系统不被压垮;
- 在系统面临高并发压力时,可以暂时关闭一些非核心功能,保证核心业务的正常运行;
- 架构部分:
- 使用容器技术(如
Docker
)进行部署,可以实现快速部署、弹性扩展和资源隔离;
- 使用容器技术(如
- 监控和预警
- 建立完善的监控系统,实时监测系统的性能指标,如 CPU 使用率、内存使用率、网络流量、数据库连接数等。当指标超过预设的阈值时,及时发出预警;
MySQL的Btree类型是如何找寻数据的?
- 初始位置:
- 查找数据时,首先从
B-tree
的根节点开始。根节点包含了指向子节点的指针以及一些索引键值;
- 查找数据时,首先从
- 键值比较:
- 将查询的键值与根节点中的键值进行比较。如果查询键值等于某个节点中的键值,就找到了对应的数据;
- 如果查询键值小于节点中的某个键值,那么沿着指向较小键值范围的子节点指针继续查找;
- 如果查询键值大于节点中的所有键值,那么沿着指向较大键值范围的子节点指针继续查找;
- 递归查找:
- 重复上述比较键值的过程,沿着合适的子节点指针不断向下遍历,直到找到目标数据或者到达叶子节点;
- 最终查找:
- 如果查找过程到达叶子节点,再次进行键值比较。在叶子节点中,通常包含了实际的数据行指针或者数据本身(如果是聚簇索引);
- 如果在叶子节点中找到了与查询键值相等的键值,就可以根据指针获取到对应的数据行;
MySQL数据库的默认事务级别是什么?
-
MySQL
数据库的默认事务隔离级别是 “可重复读”(REPEATABLE READ); -
备注:
MySQL
的其他事务级别还有- 读未提交(READ UNCOMMITTED)
- 读已提交(READ COMMITTED)
- 串行化(SERIALIZABLE)
在系统中,用户反馈界面比较卡顿,但日志中各方面都显示正常运行,你该如何去找到问题来源?
- 前端排查:
- 使用浏览器的开发者工具,检查网络请求的加载时间、页面渲染时间以及 JavaScript 执行时间。查看是否有某个资源加载过慢或者某个脚本执行时间过长;
- 分析页面的重绘和回流情况,过多的重绘和回流可能导致卡顿。可以检查是否有复杂的
CSS
选择器或者频繁的DOM
操作导致了性能问题; - 确认页面中加载的图片、脚本、样式表等资源是否过大。过大的资源文件会延长加载时间,导致页面卡顿。可以使用图像压缩工具优化图片大小,合并和压缩脚本和样式表文件;
- 检查是否存在过多的第三方插件或脚本加载,这些可能会影响页面性能。如果可能,尝试禁用一些不必要的插件进行测试;
- 后端排查:
- 虽然日志中没有明显错误,但可能存在一些慢查询没有被记录到日志中。使用数据库的性能分析工具,查看是否有耗时较长的查询语句;
- 检查数据库连接池的配置,确保连接池的大小合适,不会因为连接不足导致等待时间过长;
- 使用服务器监控工具,查看 CPU、内存、磁盘 I/O 和网络带宽的使用情况。如果某个资源的使用率过高,可能会导致系统响应变慢;
- 查服务器的负载情况,是否有其他高负载的进程影响了系统的性能。可以使用命令行工具如
top
、htop
等查看系统进程的资源使用情况;
- 网络排查:
- 使用网络测试工具,检查客户端与服务器之间的网络延迟。高延迟可能会导致页面加载缓慢和卡顿;
- 检查网络带宽的使用情况,是否有其他大量占用带宽的应用影响了系统的网络性能;
- 用户环境排查:
- 询问用户使用的浏览器类型和版本,确认是否存在特定浏览器的兼容性问题。不同的浏览器对
HTML
、CSS
和JavaScript
的实现可能会有所不同,某些功能在特定浏览器上可能会出现性能问题; - 尝试在其他浏览器上重现问题,以确定是否是浏览器特定的问题;
- 询问用户使用的浏览器类型和版本,确认是否存在特定浏览器的兼容性问题。不同的浏览器对
Cookie和Session的区别和应用范围?
Cookie
和Session
的区别:- 存储位置:
Cookie
存储在浏览器中,Session
存在于服务器端; - 存储容量:
Cookie
的存储容量相对来说有限,Session
的存储容量取决于服务器的内存和存储设置; - 安全性:
Cookie
容易被用户篡改或者窃取,Session
相对来说更加安全; - 生命周期:
Cookie
可以长期存在或者关闭浏览器删除,Session
可以在服务器中根据需要设置超时时间;
- 存储位置:
- 应用范围:
Cookie
:储存用户的偏好设置,或者购物车的商品信息,分析用户的浏览行为和爱好等;Session
:储存用户的登录状态,或者多页面的事务处理,以及防止表单的重复提交;
在什么情况下需要使用到final关键字?
- 当你希望一个变量的值在初始化后不能被改变时,可以将其声明为
final
; - 在方法内部,如果不希望一个局部变量在初始化后被重新赋值,可以使用
final
修饰; - 当你不希望子类重写某个特定的方法时,可以将该方法声明为
final
; - 对于一些性能关键的方法,将其声明为
final
可以让编译器进行一些优化(原因:因为编译器知道这个方法不能被重写,所以可以更积极地进行内联等优化操作,从而提高程序的执行效率); - 当你希望一个类不能被其他类继承时,可以将该类声明为
final
;
反射机制的优缺点?
- 优点:
- 反射机制使得程序能够在运行时根据不同的条件加载不同的类、创建对象并调用其方法;
- 在面对需求变化时,反射机制可以让程序更容易进行扩展和修改;
- Spring 框架利用反射来实现依赖注入,测试框架如 JUnit 使用反射来运行测试方法;
- 可以通过反射访问类的私有成员,包括私有方法和私有字段;
- 缺点:
- 反射操作相对直接的代码调用来说比较耗时。因为在运行时,反射需要解析类的结构、查找方法和字段等信息,这涉及到更多的计算和内存访问;
- 通过反射可以访问类的私有成员,这可能破坏了类的封装性;
- 恶意代码可以利用反射来访问和修改敏感的系统资源或执行未经授权的操作;
- 使用反射的代码通常比较复杂,难以理解和维护;这使得代码的可读性降低,增加了开发和调试的难度;
- 由于反射是在运行时进行的,编译器无法对反射代码进行全面的类型检查和错误检测。这可能导致在运行时出现
ClassNotFoundException
、NoSuchMethodException
等异常;
Spring是如何实现事务的?并且Spring的事务在哪些情况下会失效?
Spring
实现事务的方式:- 配置事务管理器:
Spring
使用DataSourceTransactionManager
实现JDBC
的事务管理,JpaTransactionManager
实现JPA
的事务管理; - 声明事务属性:在方法上添加
@Transactional
注解或在 XML 配置中定义事务属性,来指定事务的传播行为、隔离级别、超时时间等; - 生成代理对象:当一个被
@Transactional
注解的方法被调用时,代理对象会根据事务属性开启事务、执行方法,并在方法正常执行完毕或出现异常时提交或回滚事务;
- 配置事务管理器:
Spring
事务失效的情况:Spring
的事务管理是通过动态代理实现的,而代理对象只能拦截public
方法的调用;- 在同一个类中,一个方法调用另一个被
@Transactional
注解的方法时,事务不会生效; - 如果在事务方法中捕获了异常但没有重新抛出,那么事务不会回滚;
- 如果使用的数据库不支持事务,那么 Spring 的事务管理也不会生效;
- 如果事务的传播行为配置不当,也可能导致事务失效;
为什么Redis使用单线程的时候性能会优于多线程?
- 避免线程切换开销
- 在多线程环境中,
CPU
需要在不同的线程之间进行切换,这会带来一定的开销; - 单线程可以使
CPU
始终专注于处理Redis
的任务,无需花费时间在线程切换的上下文保存和恢复上;
- 在多线程环境中,
- 避免线程安全问题
- 多线程编程需要考虑线程安全问题,例如对共享数据的同步和互斥访问;
Redis
使用单线程模型,不存在线程安全问题,因此代码实现更加简单直接;
- 内存分配和回收
- 由于只有一个线程在操作内存,不会出现多个线程同时申请或释放内存的情况,从而减少了内存碎片的产生,提高了内存的使用效率;
this和super的区别是什么?
this
指代当前对象,super
指代当前对象的父类对象;- 在普通方法中,
this
可以用来区分局部变量和成员变量。当方法中的局部变量与成员变量同名时,可以使用this.成员变量名
来明确访问成员变量; - 在子类中,如果子类和父类有同名的成员变量或方法,可以使用
super.成员变量名
或super.方法名
来访问父类的成员变量或方法。
- 在普通方法中,
this
的使用场景:- 解决变量命名冲突:如前面提到的局部变量与成员变量同名的情况;
- 作为方法参数传递当前对象:有时候需要将当前对象作为参数传递给其他方法,可以使用
this
。例如,在一些事件处理机制中,将当前对象传递给监听器方法; - 返回当前对象:在一些链式调用的方法中,可以让方法返回
this
,以便在一行代码中连续调用多个方法;
super
的使用场景:- 调用父类的构造函数:确保在子类对象创建时,先初始化父类的部分;
- 访问被重写的父类方法:当子类重写了父类的方法,但在某些情况下需要调用父类的原始方法时,可以使用
super
; - 访问父类的成员变量:当子类和父类有同名的成员变量,需要明确访问父类的成员变量时使用;
Redis过期键的删除策略是什么?
- 定时删除:在设置键的过期时间时,创建一个定时器,当过期时间到达时,立即删除该键;
- 惰性删除:只有在访问一个键时,才会检查该键是否过期。如果过期,则进行删除操作;
- 定期删除:
Redis
会周期性地随机检查一部分设置了过期时间的键,并删除其中过期的键;
JAVA为什么不能真正的去实现泛型?
Java 中的泛型在编译期进行类型检查和类型擦除。这意味着在编译后生成的字节码中,泛型信息被擦除,只保留原始类型。例如,List<String>
和 List<Integer>
在编译后都变为 List<Object>
。这样做主要有以下几个原因:
Java
在引入泛型之前已经有大量的代码在使用。如果完全实现泛型而不进行类型擦除,那么所有旧版本的代码在新版本的 Java 环境中可能无法正常运行。通过类型擦除,可以确保旧代码在新的泛型环境中仍然能够工作,只需要在编译时进行一些警告提示。- 避免在运行时进行复杂的类型处理可以提高性能。如果在运行时保留完整的泛型信息,会增加运行时的开销,包括内存占用和运行时类型检查的时间成本。
- 通过类型擦除,Java 可以在运行时使用更高效的通用数据结构和算法,而不需要为每种具体的泛型类型都生成独立的代码。
如何保证Redis和数据库的双写一致性?
- 先更新数据库,再更新
Redis
; - 先删除
Redis
的数据,再更新数据库; - 使用订阅数据库
binlog
的方式同步数据; - 设置合理的过期时间;
SpringBuffer和StringBulider的区别是什么?
SpringBuffer
是线程安全的,适合在多线程环境下,当多个线程可能同时访问和修改同一个字符串缓冲区时使用;StringBuilder
是非线程安全的,适用于单线程环境,在性能要求较高且不需要考虑线程安全问题的情况下使用。StringBuilder
通常比StringBuffer
具有更好的性能。因为它不需要进行线程安全的同步操作,所以在执行字符串拼接等操作时速度更快;StringBuffer
适用于多线程环境下,需要保证字符串操作的线程安全的场景;StringBuilder
适用于单线程环境下,对性能要求较高的场景;
使用JAVA创建线程有哪几种方式?
- 继承
Thread
类- 定义一个类继承自
java.lang.Thread
类。 - 重写
run()
方法,在这个方法中编写线程要执行的任务代码。 - 创建该类的实例,然后调用
start()
方法启动线程。
- 定义一个类继承自
- 实现 Runnable 接口
- 定义一个类实现
java.lang.Runnable
接口。 - 实现接口中的
run()
方法,编写线程要执行的任务代码。 - 创建一个
Thread
对象,将实现了Runnable
接口的类的实例作为参数传递给Thread
的构造函数。 - 调用
Thread
对象的start()
方法启动线程。
- 定义一个类实现
- 使用
FutureTask
和Callable
接口- 定义一个类实现
java.util.concurrent.Callable
接口,与Runnable
接口类似,不同的是Callable
接口的call()
方法有返回值并且可以抛出异常。 - 在实现类中实现
call()
方法,编写线程要执行的任务代码并返回结果。 - 创建一个
FutureTask
对象,将实现了Callable
接口的类的实例作为参数传递给FutureTask
的构造函数。 - 创建一个
Thread
对象,将FutureTask
对象作为参数传递给Thread
的构造函数。 - 调用
Thread
对象的start()
方法启动线程。 - 可以通过
FutureTask
对象的get()
方法获取线程执行的结果,如果线程尚未执行完毕,get()
方法会阻塞直到线程执行完成并返回结果。
- 定义一个类实现