- 为什么ArrayList不是线程安全的,具体来说是哪里不安全?
2. 比较 HashSet、LinkedHashSet 和 TreeSet 三者的异同
HashMap 的底层实现
HashMap 的长度为什么是 2 的幂次方
列举HashMap在多线程下可能会出现的问题?
7. ConcurrentHashMap 和 Hashtable 的区别
- ConcurrentHashMap已经用了synchronized,为什么还要用CAS(乐观锁)呢
为什么HashMap要用红黑树而不是平衡二叉树?
HashMap的扩容机制介绍一下
请简要描述线程与进程的关系,区别及优缺点?
- 说说线程的生命周期和状态?
7. Thread#sleep() 方法和 Object#wait() 方法对比
- 可以直接调用 Thread 类的 run 方法吗?
4. synchronized 和 volatile 有什么区别?
synchronized 和 ReentrantLock 有什么区别?
ThreadLocal 内存泄露问题是怎么导致的?如何避免?
start启动线程,线程池:submit对于callable,execute对于runable。
3. 一个任务需要依赖另外两个任务执行完之后再执行,怎么设计?
死亡对象判断方法
如何判断一个类是无用的类?
垃圾收集器
类加载过程
JVM 中内置了三个重要的 ClassLoader
类加载器和双亲委派机制。
1. 说说自己对于 Spring MVC 了解?
MVC 是模型(Model)、视图(View)、控制器(Controller)的简写,其核心思想是通过将业务逻辑、数据、显示分离来组织代码。
3. 将一个类声明为 Bean 的注解有哪些?
@Component
:通用的注解,可标注任意类为Spring
组件。如果一个 Bean 不知道属于哪个层,可以使用@Component
注解标注。@Repository
: 对应持久层即 Dao 层,主要用于数据库相关操作。@Service
: 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。@Controller
: 对应 Spring MVC 控制层,主要用于接受用户请求并调用Service
层返回数据给前端页面。
11. Bean 的生命周期了解么?
创建 Bean 的实例:Bean 容器首先会找到配置文件中的 Bean 定义,然后使用 Java 反射 API 来创建 Bean 的实例。
Bean 属性赋值/填充:为 Bean 设置相关属性和依赖,例如
@Autowired
等注解注入的对象、@Value
注入的值、setter
方法或构造函数注入依赖和值、@Resource
注入的各种资源。Bean 初始化
- 如果 Bean 实现了
BeanNameAware
接口,调用setBeanName()
方法,传入 Bean 的名字。 - 如果 Bean 实现了
BeanClassLoaderAware
接口,调用setBeanClassLoader()
方法,传入ClassLoader
对象的实例。 - 如果 Bean 实现了
BeanFactoryAware
接口,调用setBeanFactory()
方法,传入BeanFactory
对象的实例。 - 与上面的类似,如果实现了其他
*.Aware
接口,就调用相应的方法。 - 如果有和加载这个 Bean 的 Spring 容器相关的
BeanPostProcessor
对象,执行postProcessBeforeInitialization()
方法 - 如果 Bean 实现了
InitializingBean
接口,执行afterPropertiesSet()
方法。 - 如果 Bean 在配置文件中的定义包含
init-method
属性,执行指定的方法。 - 如果有和加载这个 Bean 的 Spring 容器相关的
BeanPostProcessor
对象,执行postProcessAfterInitialization()
方法。
- 如果 Bean 实现了
销毁 Bean
:销毁并不是说要立马把 Bean 给销毁掉,而是把 Bean 的销毁方法先记录下来,将来需要销毁 Bean 或者销毁容器的时候,就调用这些方法去释放 Bean 所持有的资源。
- 如果 Bean 实现了
DisposableBean
接口,执行destroy()
方法。 - 如果 Bean 在配置文件中的定义包含
destroy-method
属性,执行指定的 Bean 销毁方法。或者,也可以直接通过@PreDestroy
注解标记 Bean 销毁之前执行的方法。
- 如果 Bean 实现了
整体上可以简单分为四步:实例化 —> 属性赋值 —> 初始化 —> 销毁。
初始化这一步涉及到的步骤比较多,包含
Aware
接口的依赖注入、BeanPostProcessor
在初始化前后的处理以及InitializingBean
和init-method
的初始化操作。销毁这一步会注册相关销毁回调接口,最后通过
DisposableBean
和destory-method
进行销毁。
3. SpringMVC 工作原理了解吗?
- 客户端(浏览器)发送请求,
DispatcherServlet
拦截请求。 DispatcherServlet
根据请求信息调用HandlerMapping
。HandlerMapping
根据 URL 去匹配查找能处理的Handler
(也就是我们平常说的Controller
控制器) ,并会将请求涉及到的拦截器和Handler
一起封装。DispatcherServlet
调用HandlerAdapter
适配器执行Handler
。Handler
完成对用户请求的处理后,会返回一个ModelAndView
对象给DispatcherServlet
,ModelAndView
顾名思义,包含了数据模型以及相应的视图的信息。Model
是返回的数据对象,View
是个逻辑上的View
。ViewResolver
会根据逻辑View
查找实际的View
。DispaterServlet
把返回的Model
传给View
(视图渲染)。- 把
View
返回给请求者(浏览器)
上述流程是传统开发模式(JSP,Thymeleaf 等)的工作原理。
对于前后端分离时,后端通常不再返回具体的视图,而是返回纯数据(通常是 JSON 格式,Spring 会自动将其转换为 JSON 格式),由前端负责渲染和展示。
1. Spring 循环依赖了解吗,怎么解决?
两个或多个 Bean 之间相互持有对方的引用。
@Component
public class CircularDependencyA {
@Autowired
private CircularDependencyB circB;
}
@Component
public class CircularDependencyB {
@Autowired
private CircularDependencyA circA;
}
有三级缓存:
- 一级缓存(singletonObjects):存放最终形态的 Bean(已经实例化、属性填充、初始化),单例池,为“Spring 的单例属性”⽽⽣。一般情况我们获取 Bean 都是从这里获取的,但是并不是所有的 Bean 都在单例池里面,例如原型 Bean 就不在里面。
- 二级缓存(earlySingletonObjects):存放过渡 Bean(半成品,尚未属性填充,未进行依赖注入),也就是三级缓存中
ObjectFactory
产生的对象,与三级缓存配合使用的,可以防止 AOP 的情况下,每次调用ObjectFactory#getObject()
都是会产生新的代理对象的。 - 三级缓存(singletonFactories):存放
ObjectFactory
,ObjectFactory
的getObject()
方法(最终调用的是getEarlyBeanReference()
方法)可以生成原始 Bean 对象或者代理对象(如果 Bean 被 AOP 切面代理)。三级缓存只会对单例 Bean 生效。
- 先去 一级缓存
singletonObjects
中获取,存在就返回; - 如果不存在或者对象正在创建中,于是去 二级缓存
earlySingletonObjects
中获取; - 如果还没有获取到,就去 三级缓存
singletonFactories
中获取,通过执行ObjectFacotry
的getObject()
就可以获取该对象,获取成功之后,从三级缓存移除B,并将B对象加入到二级缓存中。
步骤 1:创建 Bean A
- Spring 容器开始创建 Bean A,首先将 Bean A 的创建状态标记为 “正在创建”。
- 实例化 Bean A,将 Bean A 的早期引用(一个
ObjectFactory
对象)放入三级缓存singletonFactories
中。这个ObjectFactory
对象可以在需要时返回 Bean A 的早期实例。 - 开始对 Bean A 进行属性注入,发现 Bean A 依赖于 Bean B。
步骤 2:创建 Bean B
- Spring 容器开始创建 Bean B,同样将 Bean B 的创建状态标记为 “正在创建”。
- 实例化 Bean B,将 Bean B 的早期引用放入三级缓存
singletonFactories
中。 - 开始对 Bean B 进行属性注入,发现 Bean B 依赖于 Bean A。
步骤 3:解决 Bean B 对 Bean A 的依赖
- 当 Bean B 需要注入 Bean A 时,Spring 容器首先从一级缓存
singletonObjects
中查找 Bean A,发现没有找到。 - 接着从二级缓存
earlySingletonObjects
中查找,也没有找到。 - 然后从三级缓存
singletonFactories
中查找,找到了 Bean A 的早期引用(ObjectFactory
对象)。 - 调用
ObjectFactory
的getObject()
方法,获取 Bean A 的早期实例。如果 Bean A 需要进行 AOP 代理,会在这里创建代理对象,并将代理对象放入二级缓存earlySingletonObjects
中,同时从三级缓存singletonFactories
中移除该引用。 - 将获取到的 Bean A 的早期实例注入到 Bean B 中。
步骤 4:完成 Bean B 的创建
- Bean B 完成属性注入后,进行初始化操作。
- 将完全创建好的 Bean B 实例放入一级缓存
singletonObjects
中,并从二级缓存earlySingletonObjects
和三级缓存singletonFactories
中移除相关引用。
步骤 5:完成 Bean A 的创建
- 由于 Bean B 已经创建完成,将 Bean B 实例注入到 Bean A 中。
- Bean A 完成属性注入后,进行初始化操作。
- 将完全创建好的 Bean A 实例放入一级缓存
singletonObjects
中,并从二级缓存earlySingletonObjects
和三级缓存singletonFactories
中移除相关引用。
1. 什么是 Spring Boot Starters?
Spring Boot Starters 是一组便捷的依赖描述符,它们预先打包了常用的库和配置。当我们开发 Spring 应用时,只需添加一个 Starter 依赖项,即可自动引入所有必要的库和配置,快速引入相关功能。
在没有 Spring Boot Starters 之前,开发一个 RESTful 服务或 Web 应用程序通常需要手动添加多个依赖,比如 Spring MVC、Tomcat、Jackson 等。这不仅繁琐,还容易导致版本不兼容的问题。而有了 Spring Boot Starters,我们只需添加一个依赖,如 spring-boot-starter-web,即可包含所有开发 REST 服务所需的库和依赖。
这个 spring-boot-starter-web 依赖包含了 Spring MVC(用于处理 Web 请求)、Tomcat(默认嵌入式服务器)、Jackson(用于 JSON 处理)等依赖项。这种方式极大地简化了开发过程,让我们可以更加专注于业务逻辑的实现。
2. 介绍一下@SpringBootApplication 注解
- @EnableAutoConfiguration: 启用 Spring Boot 的自动配置机制。它是自动配置的核心,允许 Spring Boot 根据项目的依赖和配置自动配置 Spring 应用的各个部分。
- @ComponentScan: 启用组件扫描,扫描被 @Component(以及 @Service、@Controller 等)注解的类,并将这些类注册为 Spring 容器中的 Bean。默认情况下,它会扫描该类所在包及其子包下的所有类。
- @Configuration: 允许在上下文中注册额外的 Bean 或导入其他配置类。它相当于一个具有 @Bean 方法的 Spring 配置类。
3. Spring Boot 的自动配置是如何实现的?
Spring Boot 通过@EnableAutoConfiguration
开启自动装配,通过 SpringFactoriesLoader 最终加载META-INF/spring.factories
中的自动配置类实现自动装配.
HTTP和HTTPS二者区别
- HTTP 是超文本传输协议,信息是明文传输,存在安全风险的问题。HTTPS 则解决 HTTP 不安全的缺陷,在 TCP 和 HTTP 网络层之间加入了 SSL/TLS 安全协议,使得报文能够加密传输。
- HTTP 连接建立相对简单, TCP 三次握手之后便可进行 HTTP 的报文传输。而 HTTPS 在 TCP 三次握手之后,还需进行 SSL/TLS 的握手过程,才可进入加密报文传输。
- 两者的默认端口不一样,HTTP 默认端口号是 80,HTTPS 默认端口号是 443。
- HTTPS 协议需要向 CA(证书权威机构)申请数字证书,来保证服务器的身份是可信的。
对称+非对称、摘要算法+数字签名、身份证书
TCP、UDP区别、应用场景
MySQL 基础架构
1. 结构
- 连接器: 身份认证和权限相关(登录 MySQL 的时候)。
- 查询缓存: 执行查询语句的时候,会先查询缓存(MySQL 8.0 版本后移除,因为这个功能不太实用)。
- 分析器: 没有命中缓存的话,SQL 语句就会经过分析器,分析器说白了就是要先看你的 SQL 语句要干嘛,再检查你的 SQL 语句语法是否正确。
- 优化器: 按照 MySQL 认为最优的方案去执行。
- 执行器: 执行语句,然后从存储引擎返回数据。 执行语句之前会先判断是否有权限,如果没有权限的话,就会报错。
- 插件式存储引擎:主要负责数据的存储和读取,采用的是插件式架构,支持 InnoDB、MyISAM、Memory 等多种存储引擎。InnoDB 是 MySQL 的默认存储引擎,绝大部分场景使用 InnoDB 就是最好的选择。
2. SQL语句在MySQL中的执行过程
- 查询语句的执行流程如下:权限校验(如果命中缓存)--->查询缓存--->分析器--->优化器--->权限校验--->执行器--->引擎
- 更新语句执行流程如下:分析器---->权限校验---->执行器--->引擎---redo log(prepare 状态)--->binlog--->redo log(commit 状态)