目录
2. 简述MyBatis的插件运行原理,如何编写一个插件?
4. MyBatis能执行一对一、一对多关联查询吗?实现方式及区别?
5. MyBatis如何将SQL结果封装为目标对象?映射形式?
6. MyBatis映射文件中,A标签通过include引用B标签,B能否定义在A后面?
8. MyBatis的Executor执行器有哪些?区别?
9. 为什么说MyBatis是半自动ORM?与全自动的区别?
14. Spring MVC和Struts2的区别?
18. Bean工厂和ApplicationContext的区别?
21. 什么是基于Java的Spring注解配置?举例说明
22. 使用Spring通过什么方式访问Hibernate?
23. 如何通过HibernateDaoSupport整合Spring和Hibernate?
25. AOP中连接点(JoinPoint)和切入点(Pointcut)的区别
27. @Autowired和@Resource的区别
33. final关键字能否防止指令重排序?能否代替volatile?
1. 谈谈你对Spring MVC的理解?
答案:
Spring MVC是基于Java的轻量级Web框架,采用MVC架构模式:
- Model:数据模型(POJO/Map)
- View:视图渲染(JSP/Thymeleaf)
- Controller:处理请求(@Controller注解)
核心组件:
- DispatcherServlet:前端控制器,统一分发请求
- HandlerMapping:映射URL到Controller
- ViewResolver:解析逻辑视图名到物理视图
特点:
- 松耦合(通过DI和IoC)
- 支持RESTful风格
- 集成验证、拦截器等
2. 简述MyBatis的插件运行原理,如何编写一个插件?
答案:
运行原理:
基于JDK动态代理,拦截四大核心组件:
- Executor(执行SQL)
- ParameterHandler(参数处理)
- ResultSetHandler(结果集处理)
- StatementHandler(SQL构建)
编写步骤:
- 实现Interceptor接口,重写intercept方法
- 使用@Intercepts注解声明拦截目标
- 在mybatis-config.xml中注册插件
示例代码:
@Intercepts({@Signature(type=Executor.class, method="update", args={MappedStatement.class, Object.class})})
public class MyPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 前置处理
Object result = invocation.proceed(); // 执行原方法
// 后置处理
return result;
}
}
3. MyBatis是否支持延迟加载?实现原理?
答案:
支持延迟加载(懒加载),通过动态代理实现:
- 配置:在mybatis-config.xml中启用:
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
原理:
-
- 查询主对象时,返回代理对象(如Proxy.newProxyInstance)
- 当访问关联对象时,触发代理逻辑执行额外SQL(只有在代码实际访问关联属性时(如order.getDetails()),MyBatis才会通过动态代理触发二次SQL查询,实现按需加载。)
4. MyBatis能执行一对一、一对多关联查询吗?实现方式及区别?
答案:
支持两种方式:
- 嵌套查询(分步查询)
- 通过<association>(一对一)或<collection>(一对多)
- 执行多条SQL,可能产生N+1问题
- 嵌套结果(联合查询)
- 单条SQL联表查询,通过resultMap手动映射
- 性能更高,但SQL复杂度增加
区别:
方式 |
性能 |
复杂度 |
适用场景 |
嵌套查询 |
较低 |
简单 |
关联数据较少时 |
嵌套结果 |
高 |
复杂 |
需要优化查询性能 |
5. MyBatis如何将SQL结果封装为目标对象?映射形式?
答案:
封装过程:
- 通过ResultSetHandler处理JDBC结果集
- 根据resultType或resultMap映射字段到对象属性
映射形式:
- 自动映射:字段名与属性名一致(如user_name → userName)
- 手动映射:<resultMap>显式配置字段与属性对应关系
- 构造函数映射:通过<constructor>标签匹配构造参数
6. MyBatis映射文件中,A标签通过include引用B标签,B能否定义在A后面?
答案:
可以。MyBatis解析XML时会将所有标签加载到内存,因此<sql id="B">可以定义在<include refid="B">之后,无顺序要求。
7. MyBatis动态SQL怎么设定?语法?
答案:
动态SQL标签:
- <if>:条件判断
- <choose>/<when>/<otherwise>:多分支选择
- <foreach>:遍历集合(如IN查询)
- <where>/<set>`:智能处理前缀后缀
示例:
<select id="findUser" parameterType="map" resultType="User">
SELECT * FROM user
<where>
<if test="name != null">AND name = #{name}</if>
<if test="age != null">AND age = #{age}</if>
</where>
</select>
8. MyBatis的Executor执行器有哪些?区别?
答案:
Executor 是 MyBatis 执行 SQL 的核心组件,负责 SQL 执行、缓存、事务和延迟加载。
三种执行器:
- SimpleExecutor:默认,每次执行新PreparedStatement
- ReuseExecutor:复用预处理语句(缓存Statement)
- BatchExecutor:批量执行更新操作
区别:
执行器 |
性能 |
适用场景 |
SimpleExecutor |
一般 |
简单查询 |
ReuseExecutor |
较高 |
高频重复SQL |
BatchExecutor |
最高 |
批量插入/更新 |
9. 为什么说MyBatis是半自动ORM?与全自动的区别?
答案:
ORM(Object-Relational Mapping,对象关系映射) 是一种编程技术,用于在 面向对象编程语言(如 Java、Python)和 关系型数据库(如 MySQL、PostgreSQL)之间建立映射关系,使开发者能够以操作对象的方式操作数据库,而无需直接编写 SQL。
半自动ORM:
- 需手动编写SQL和映射(灵活控制SQL优化)
- 如MyBatis、JdbcTemplate
全自动ORM:
- 自动生成SQL(如Hibernate的HQL)
- 牺牲灵活性换取开发效率
核心区别:
特性 |
半自动ORM |
全自动ORM |
SQL控制 |
开发者手动编写 |
框架自动生成 |
性能优化 |
更灵活 |
受限于框架实现 |
10. 简单介绍MyBatis的理解?
答案:
MyBatis是持久层框架,核心特点:
- SQL与代码分离:通过XML/注解配置SQL
- 结果集映射:灵活的结果集到对象映射
- 动态SQL:支持条件拼接
- 插件扩展:可拦截核心组件逻辑
与JDBC对比优势:
- 减少样板代码(如Connection管理)
- 内置连接池、缓存机制
11. 介绍一下Spring的事务管理?
答案:
编程式事务侵入到了业务代码里面,但是提供了更加详细的事务管理;而声明式事务由于基于AOP,所以既能起到事务管理的作用,又可以不影响业务代码的具体实现。
Spring事务管理的核心是声明式事务(通过注解或XML配置)和编程式事务(通过代码控制)。
关键特性:
- 事务传播行为(如REQUIRED、REQUIRES_NEW)
- 隔离级别(如READ_COMMITTED、SERIALIZABLE)
- 回滚规则(指定哪些异常触发回滚)
实现方式:
- 注解驱动:
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT)
public void transferMoney() { ... }
- XML配置:
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="transfer*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
底层依赖:
- 使用AOP拦截@Transactional方法,通过PlatformTransactionManager(如DataSourceTransactionManager)管理事务。
12. SSM优缺点、使用场景?
答案:
SSM框架组合:Spring(IoC/AOP) + Spring MVC(Web层) + MyBatis(持久层)。
框架 |
优点 |
缺点 |
使用场景 |
Spring |
松耦合、易扩展 |
配置复杂(XML/注解混合) |
需要依赖管理的企业级应用 |
Spring MVC |
RESTful支持、灵活视图解析 |
学习曲线较陡 |
需要精细控制HTTP请求的应用 |
MyBatis |
SQL可控、性能优化灵活 |
需手动编写SQL和映射 |
复杂SQL或需深度优化的项目 |
典型场景:电商后台、金融系统等需要平衡灵活性与性能的项目。
13. Spring MVC的工作流程?
答案:
核心流程(简化版):
- DispatcherServlet接收HTTP请求。
- HandlerMapping解析URL,找到对应@Controller。
- Controller处理请求,返回逻辑视图名或@ResponseBody数据。
- ViewResolver将逻辑视图名解析为物理视图(如JSP路径)。
- View渲染响应,返回客户端。
关键扩展点:
- 拦截器(HandlerInterceptor)
- 异常处理器(@ExceptionHandler)
14. Spring MVC和Struts2的区别?
答案:
特性 |
Spring MVC |
Struts2 |
架构 |
基于Servlet API |
基于Filter链 |
性能 |
更高(无拦截器栈开销) |
较低(OGNL表达式解析慢) |
配置 |
注解驱动为主 |
XML配置为主 |
线程安全 |
Controller默认单例 |
Action每次请求创建实例 |
趋势:Spring MVC已成为主流,Struts2因安全漏洞逐渐淘汰。
15. 如何把数据放入Session?
答案:
Spring MVC中两种方式:
- 直接操作HttpSession:
@Controller
public class UserController {
@RequestMapping("/login")
public String login(HttpSession session) {
session.setAttribute("user", user);
return "home";
}
}
- 使用@SessionAttributes注解(仅限Controller内共享):
@SessionAttributes("user")
public class UserController { ... }
16. Spring MVC的执行流程?
答案:
(与第13题部分重复,此处补充细节)
- 请求到达:DispatcherServlet#doDispatch()处理。
- 拦截器:执行preHandle()。
- 参数绑定:HandlerAdapter处理@RequestParam等注解。
- 返回值处理:
- 若返回String,走视图解析流程;
- 若返回@ResponseBody,由HttpMessageConverter序列化为JSON/XML。
17. MyBatis的好处是什么?
答案:
- SQL可控:避免全自动ORM的复杂查询性能问题。
- 动态SQL:灵活拼接条件。
- 轻量级:无侵入性,与Spring集成简单。
- 缓存机制:一级缓存(Session级)、二级缓存(全局)。
18. Bean工厂和ApplicationContext的区别?
答案:
特性 |
BeanFactory(基础接口) |
ApplicationContext(扩展接口) |
Bean加载时机 |
懒加载(getBean时初始化) |
预加载(启动时初始化所有Bean) |
国际化支持 |
无 |
有(MessageSource) |
事件机制 |
无 |
支持ApplicationEvent发布订阅 |
推荐:生产环境优先用ApplicationContext(如ClassPathXmlApplicationContext)。
19. Spring支持的Bean作用域?
答案:
- singleton(默认):单例,IoC容器中唯一实例。
- prototype:每次请求创建新实例。
- request:HTTP请求生命周期(Web环境)。
- session:HTTP会话生命周期(Web环境)。
- application:ServletContext生命周期(Web环境)。
配置方式:
@Scope("prototype")
@Component
public class UserService { ... }
20. 什么是Bean的自动装配?
答案:
自动装配(Autowiring):Spring自动注入依赖的Bean,无需显式配置。
四种模式:
- byType:按类型匹配(需唯一Bean)。
- byName:按属性名匹配Bean ID。
- constructor:构造函数参数自动装配。
- no(默认):手动配置依赖。
注解驱动:
@Service
public class OrderService {
@Autowired // byType优先
private UserService userService;
}
限制:
- 歧义性需配合@Qualifier解决。
- 基本类型(如int)无法自动装配。
21. 什么是基于Java的Spring注解配置?举例说明
答案:
基于Java的配置是通过@Configuration类替代XML文件,核心注解包括:
- @Configuration:标记类为配置源
- @Bean:定义Bean实例(方法级别)
- @ComponentScan:自动扫描组件
- @PropertySource:加载配置文件
示例:
@Configuration
@ComponentScan("com.example")
@PropertySource("classpath:app.properties")
public class AppConfig {
@Bean
public DataSource dataSource() {
return new DriverManagerDataSource(url, user, pwd);
}
}
22. 使用Spring通过什么方式访问Hibernate?
答案:
两种主要方式:
- HibernateTemplate(已淘汰):
@Autowired
private HibernateTemplate hibernateTemplate;
public User getById(Long id) {
return hibernateTemplate.get(User.class, id);
}
原生Hibernate API(推荐):
-
- 通过LocalSessionFactoryBean配置SessionFactory
- 使用@Transactional管理事务
23. 如何通过HibernateDaoSupport整合Spring和Hibernate?
答案:
步骤:
- 继承HibernateDaoSupport类
- 注入SessionFactory
- 通过getHibernateTemplate()操作数据库
示例:
@Repository
public class UserDaoImpl extends HibernateDaoSupport implements UserDao {
@Autowired
public void setSessionFactoryDI(SessionFactory sf) {
super.setSessionFactory(sf);
}
public List<User> findAll() {
return getHibernateTemplate().loadAll(User.class);
}
}
24. Spring框架事务管理的优点
答案:
优点 |
说明 |
统一API |
支持多种持久层技术(JDBC/Hibernate/JPA) |
声明式事务 |
通过注解解耦业务代码与事务逻辑 |
传播行为控制 |
支持7种事务传播级别 |
集成测试 |
与Spring Test模块无缝协作 |
25. AOP中连接点(JoinPoint)和切入点(Pointcut)的区别
答案:
概念 |
定义 |
示例 |
连接点 |
程序执行过程中的特定点(如方法调用、异常抛出) |
UserService.login()方法执行 |
切入点 |
通过表达式匹配的连接点集合 |
@Pointcut("execution(* com.service.*.*(..))") |
26. AOP的作用及核心概念
答案:
三大作用:
- 解耦横切关注点(日志/事务/安全)
- 动态增强对象功能
- 避免重复代码
核心概念:
- 切面(Aspect):模块化横切逻辑的类(@Aspect),切入点+通知
- 通知(Advice):增强的具体实现(前置/后置/环绕等)
- 织入(Weaving):将切面应用到目标对象的过程
27. @Autowired和@Resource的区别
答案:
特性 |
@Autowired |
@Resource |
来源 |
Spring框架 |
JSR-250标准 |
注入方式 |
默认byType |
默认byName |
required属性 |
支持 |
不支持 |
名称指定 |
需配合@Qualifier |
直接通过name属性 |
28. TCP协议字段及可靠传输机制
答案:
关键字段:
- 序列号/确认号:保证数据有序到达
- 标志位(SYN/ACK/FIN):控制连接状态
- 窗口大小:流量控制
可靠传输机制:
- 三次握手建立连接
- 超时重传
- 滑动窗口控制流量
- 四次挥手释放连接
滑动窗口作用:
- 解决网络拥塞
- 提高信道利用率
29. 三次握手与四次挥手
答案:
三次握手流程:
- Client→Server:SYN=1, seq=x
- Server→Client:SYN=1, ACK=1, seq=y, ack=x+1
- Client→Server:ACK=1, seq=x+1, ack=y+1
四次挥手流程:
- Client→Server:FIN=1, seq=u
- Server→Client:ACK=1, ack=u+1
- Server→Client:FIN=1, seq=v
- Client→Server:ACK=1, ack=v+1
30. 深拷贝与浅拷贝
答案:
类型 |
特点 |
实现方式 |
浅拷贝 |
浅拷贝只复制对象的引用,而不复制对象本身。也就是说,新旧对象共享同一块内存。如果修改了新对象,原对象也会受到影响。浅复制适用于对象的属性是基本类型的情况,但如果属性是引用类型,则会导致两个对象共享同一个引用。 |
Object.clone() |
深拷贝 |
深拷贝会创建一个完全独立的新对象,新对象与原对象不共享内存。修改新对象不会影响原对象。深复制适用于对象的属性是引用类型的情况,因为它会递归地复制所有层级的对象属性和数组元素。 |
序列化/手动递归复制 |
示例:
// 深拷贝实现
public class DeepCopy implements Serializable {
public Object copy() throws IOException, ClassNotFoundException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return ois.readObject();
}
}
31. 访问器如何保障线程安全?举例说明
答案:
线程安全访问器的实现方式:
- 同步方法(synchronized):
public class Counter {
private int count;
public synchronized void increment() {
count++;
}
}
- 锁机制(ReentrantLock):
private Lock lock = new ReentrantLock();
public void safeAccess() {
lock.lock();
try {
// 临界区代码
} finally {
lock.unlock();
}
}
- 原子类(AtomicInteger等):
private AtomicInteger atomicCount = new AtomicInteger();
public void add() {
atomicCount.incrementAndGet();
}
典型场景:多线程环境下的计数器、共享资源配置。
32. 抽象类和接口的异同
答案:
在面向对象编程中,抽象类(Abstract Class)和接口(Interface)是两种不同的概念,它们都可以用来定义类的行为。抽象类是一种不能被实例化的类,它可以包含抽象方法和具体方法,而接口则是一种特殊的类,只包含抽象方法和常量。
抽象类的特点
抽象类使用 abstract 关键字定义。
抽象类中可以包含抽象方法和具体方法。
抽象类不能直接实例化,需要通过子类来实例化。
子类继承抽象类必须实现所有的抽象方法,除非子类也是抽象类。
抽象类的构造方法可以被定义,用于子类调用。
接口的特点
接口使用 interface 关键字定义。
接口只能包含抽象方法和常量(在Java 8之前)。
接口不能被实例化,也不能定义构造方法。
一个类可以实现多个接口。
接口中的方法默认是 public abstract,变量默认是 public static final。
抽象类与接口的主要区别
实现方式:类可以实现多个接口,但只能继承一个抽象类。
方法定义:抽象类可以有具体方法的实现,而接口只能有抽象方法(Java 8之后,接口也可以有默认方法和静态方法)。
变量定义:抽象类中可以有各种类型的变量,接口中的变量默认是 public static final 类型。
构造方法:抽象类可以有构造方法,接口不能有构造方法。
访问修饰符:抽象类的访问修饰符可以是 public 或默认,接口中的方法和变量默认是 public。
特性 |
抽象类 |
接口(Java 8+) |
实例化 |
不能实例化 |
不能实例化 |
方法实现 |
可包含具体方法 |
默认方法(default)可实现 |
构造方法 |
有 |
无 |
多继承 |
单继承 |
多实现 |
变量类型 |
无限制 |
默认public static final |
相同点:都不能直接实例化,都可包含抽象方法。
33. final关键字能否防止指令重排序?能否代替volatile?
答案:
final与指令重排序:
final能保证构造函数内的初始化操作不会被重排序到构造函数外(JMM保证)
final vs volatile:
维度 |
final |
volatile |
可见性 |
仅保证构造完成后的可见性 |
保证实时可见性 |
原子性 |
不保证 |
保证单次读/写原子性 |
适用场景 |
不可变对象 |
多线程共享变量 |
结论:不能互相替代。
34. static修饰的模块什么时候初始化?
答案:
初始化时机:
- 静态变量/代码块:类加载时按顺序初始化
- 静态内部类:首次访问时加载(延迟加载)
- 静态方法:调用时加载方法区指令
示例:
class Example {
static { System.out.println("静态块"); } // 类加载时执行
static int x = initX(); // 立即初始化
static class Inner { /* 延迟加载 */ }
}
35. 对象锁与类锁的区别
答案:
对象锁是针对对象实例的同步锁。当一个方法被synchronized修饰时,它锁定的是调用该方法的对象实例。因此,如果一个类的两个不同实例对象被两个线程访问,每个线程都会持有各自实例的锁,这允许它们并发执行。然而,如果两个线程操作的是共享数据,那么可能无法保证线程安全,因为每个对象实例有自己的锁。
类锁则是针对类的class对象的同步锁。当一个静态方法被synchronized修饰时,它锁定的是类的class对象。这意味着,无论一个类有多少个实例对象,类锁始终只有一把,所有实例共享这把锁。因此,即使是不同对象的实例,它们在访问同步的静态方法时也会被阻塞,直到持有类锁的线程释放锁。
锁类型 |
作用范围 |
锁对象 |
示例 |
对象锁 |
单个实例 |
this/实例变量 |
synchronized(this){} |
类锁 |
所有实例共享 |
Class对象 |
synchronized(Example.class){} |
关键区别:类锁会阻塞所有线程访问任何实例的同步静态方法。
36. 进程和线程的区别及通信方式
答案:
维度 |
进程 |
线程 |
资源分配 |
独立内存空间 |
共享进程资源 |
切换开销 |
高(需切换页表) |
低(仅切换栈/寄存器) |
通信方式 |
管道/信号量/共享内存/消息队列 |
wait()/notify()/Lock/Condition |
线程通信示例:
// 使用Condition
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
condition.await(); // 阻塞
condition.signal(); // 唤醒
37. 类锁和对象锁是否互斥?
答案:
不互斥。两者锁定不同的对象:
- 类锁锁定的是Class对象(存储在方法区)
- 对象锁锁定的是堆内存中的实例
示例:
synchronized(Example.class) { /* 线程A */ }
synchronized(instance) { /* 线程B可同时执行 */ }
38. 注解的作用目标
答案:
可作用的目标(通过@Target指定):
- TYPE:类/接口/枚举
- FIELD:字段
- METHOD:方法
- PARAMETER:参数
- CONSTRUCTOR:构造器
- LOCAL_VARIABLE:局部变量
示例:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {}
39. 内部类的分类及场景
答案:
类型 |
特点 |
使用场景 |
成员内部类 |
持有外部类引用 |
需要访问外部类成员时 |
静态内部类 |
不持有外部类引用 |
工具类/避免内存泄漏 |
局部内部类 |
定义在方法内 |
方法级封装 |
匿名内部类 |
无类名,即时实现 |
事件监听/临时实现接口 |
内存泄漏风险:非静态内部类默认持有外部类引用,需谨慎使用。
40. finally的作用及场景
答案:
核心作用:保证代码块必须执行(无论是否发生异常)
典型场景:
- 资源释放(IO流/数据库连接)
- 锁释放(避免死锁)
- 日志记录
注意:以下情况finally不会执行:
- System.exit()
- JVM崩溃
- 守护线程被终止
示例:
try {
// 可能抛出异常的操作
} finally {
lock.unlock(); // 确保锁释放
}
41. 栈溢出 vs 堆溢出 vs 堆外内存
答案:
类型 |
触发条件 |
错误表现 |
解决方案 |
JVM参数调整 |
栈溢出 |
递归调用过深 |
StackOverflowError |
检查递归终止条件 |
-Xss调整栈大小 |
堆溢出 |
对象过多/内存泄漏 |
OutOfMemoryError |
分析堆转储(MAT工具) |
-Xmx/-Xms调整堆大小 |
堆外内存 |
NIO的DirectBuffer未释放 |
OutOfMemoryError |
显式调用Cleaner |
-XX:MaxDirectMemorySize |
内存区域图示:
42. Java集合类框架
答案:
核心接口分类:
- Collection
- List:ArrayList(数组)、LinkedList(链表)
- Set:HashSet(哈希表)、TreeSet(红黑树)
- Map
- HashMap(数组+链表/红黑树)
- ConcurrentHashMap(分段锁/CAS)
线程安全方案对比:
Collections.synchronizedList(list); // 方法级锁
new CopyOnWriteArrayList<>(); // 写时复制
new ConcurrentHashMap<>(); // CAS+同步块
43. 泛型实现与示例
答案:
泛型栈实现:
public class GenericStack<T> {
private T[] elements;
private int top;
@SuppressWarnings("unchecked")
public GenericStack(int size) {
elements = (T[]) new Object[size]; // 泛型数组创建
}
public void push(T item) {
elements[top++] = item;
}
public T pop() {
return elements[--top];
}
}
类型擦除验证:
GenericStack<String> stack = new GenericStack<>(10);
System.out.println(stack.getClass()); // 输出class GenericStack(无类型参数)
44. 反射获取私有信息的风险控制
答案:
获取私有字段的步骤:
- 关闭安全检查提升性能:
Field field = clazz.getDeclaredField("secret");
field.setAccessible(true); // 突破private限制
- 操作完成后立即恢复访问控制:
AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
field.setAccessible(false);
return null;
});
安全建议:
- 配合SecurityManager使用
- 避免在关键系统(如支付)滥用反射
45. 线程状态转换
答案:
六种状态(Java.lang.Thread.State):
- NEW:新建未启动
- RUNNABLE:可运行(包含就绪和运行中)
- BLOCKED:等待监视器锁
- WAITING:无限期等待(wait()/join())
- TIMED_WAITING:限期等待(sleep(1000))
- TERMINATED:执行完成
状态转换图:
46. thread.start()后的状态变化
答案:
Thread t = new Thread(() -> {});
System.out.println(t.getState()); // NEW
t.start();
System.out.println(t.getState()); // RUNNABLE
底层机制:
- JVM调用本地方法start0()
- 创建新调用栈,状态变为RUNNABLE
- 等待OS线程调度器分配CPU时间片
47. 等待队列 vs 阻塞队列
答案:
在进入synchronized方法之前因为抢不到锁对象而进入阻塞状态,进入阻塞队列。进入到synchronized方法后由于调用了wait()方法而进入等待状态,此时进入等待队列,等待其它线程调用它的notify()方法将他唤醒。
特性 |
等待队列(Wait Queue) |
阻塞队列(BlockingQueue) |
数据结构 |
通过Object.wait()维护 |
显式队列(如ArrayBlockingQueue) |
唤醒机制 |
需显式调用notify() |
自动唤醒(put/take操作) |
使用场景 |
线程间通信 |
生产者-消费者模型 |
阻塞队列示例:
BlockingQueue<String> queue = new LinkedBlockingQueue<>(10);
queue.put("item"); // 阻塞直到空间可用
String item = queue.take(); // 阻塞直到元素可用
48. sleep() vs wait()
答案:
维度 |
sleep() |
wait() |
锁释放 |
不释放锁 |
释放锁 |
调用位置 |
任意位置 |
必须在同步块内 |
唤醒方式 |
超时自动唤醒 |
需notify()/notifyAll() |
所属类 |
Thread静态方法 |
Object实例方法 |
49. sleep(1)的作用与场景
答案:
核心作用:主动让出CPU时间片至少1毫秒(实际取决于系统计时器精度)
使用场景:
- 性能测试:消除线程竞争带来的误差
long start = System.nanoTime();
Thread.sleep(1); // 确保计时起点准确
// 被测代码
- 竞态条件调试:放大并发问题出现概率
- 节能控制:限制高频轮询的CPU占用
注意事项:Windows系统最小休眠时间约15ms(受HPET影响)
50. 中断机制与响应示例
答案:
中断三要素:
- thread.interrupt():设置中断标志
- thread.isInterrupted():检查中断状态
- InterruptedException:阻塞方法响应中断
示例:
Thread worker = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 重置中断状态
System.out.println("优雅退出");
}
}
});
worker.start();
worker.interrupt(); // 触发中断
中断处理最佳实践:
- 清理资源后重新设置中断状态
- 避免屏蔽InterruptedException
51. 守护线程 vs 协程
答案:
特性 |
守护线程(Daemon Thread) |
协程(Coroutine) |
调度层级 |
操作系统级调度 |
用户态调度(语言/框架级) |
资源开销 |
1MB+栈内存 |
KB级栈内存 |
切换成本 |
高(内核态切换) |
极低(无系统调用) |
典型实现 |
Java的Thread.setDaemon(true) |
Kotlin协程/Quasar纤维 |
应用场景 |
后台日志收集 |
高并发IO操作(如10万+连接) |
守护线程示例:
Thread daemon = new Thread(() -> {
while (true) {
// 执行监控任务
}
});
daemon.setDaemon(true); // JVM退出时自动终止
daemon.start();
52. 死锁条件与避免策略
答案:
四个必要条件:
- 互斥条件:资源独占
- 占有且等待:持有资源并等待其他资源
- 不可剥夺:资源只能主动释放
- 循环等待:多个线程形成环形等待链
避免方案:
// 1. 破坏循环等待(按固定顺序获取锁)
public void transfer(Account from, Account to, int amount) {
Account first = from.id < to.id ? from : to;
Account second = from.id < to.id ? to : from;
synchronized (first) {
synchronized (second) {
// 转账操作
}
}
}
// 2. 使用tryLock(破坏占有且等待)
if (lock1.tryLock(1, TimeUnit.SECONDS)) {
try {
if (lock2.tryLock(1, TimeUnit.SECONDS)) {
try {
// 临界区
} finally {
lock2.unlock();
}
}
} finally {
lock1.unlock();
}
}
53. 基本类型存储区域
答案:
存储规则:
数据类型 |
存储区域 |
示例 |
局部变量 |
栈帧中的局部变量表 |
int i = 10; |
实例字段 |
堆内存对象内 |
class A { int x; } |
静态字段 |
方法区 |
static int y; |
特殊情况:
- 被final修饰的静态基本类型:存储在方法区的常量池
- 数组类型:无论元素类型,数组对象本身在堆上分配
54. 多线程使用场景
答案:
典型应用场景及实现:
- 高并发Web服务
ExecutorService threadPool = Executors.newFixedThreadPool(200);
while (true) {
Socket socket = serverSocket.accept();
threadPool.execute(() -> handleRequest(socket));
}
- 批量数据处理
List<Data> dataList = getHugeData();
dataList.parallelStream().forEach(data -> process(data));
- 异步任务调度
CompletableFuture.supplyAsync(() -> fetchFromDB())
.thenApplyAsync(data -> transform(data))
.thenAcceptAsync(result -> sendToUI());
选型建议:
- CPU密集型:线程数 = CPU核心数 + 1
- IO密集型:线程数 = CPU核心数 * (1 + 平均等待时间/计算时间)
55. throw vs throws
答案:
维度 |
throw |
throws |
作用位置 |
方法内部 |
方法声明处 |
功能 |
主动抛出异常对象 |
声明可能抛出的异常类型 |
语法 |
throw new IOException("msg"); |
public void read() throws IOException |
处理要求 |
必须立即处理或声明throws |
调用方需处理或继续声明 |
最佳实践:
- 检查异常(Checked Exception):用throws声明
- 非检查异常(Unchecked Exception):通常不声明
56. Object类方法解析
答案:
核心方法及作用:
方法 |
功能说明 |
equals() |
对象逻辑相等性比较(默认实现为==) |
hashCode() |
返回对象哈希码(与equals()必须保持一致) |
toString() |
返回对象字符串表示(默认输出类名@哈希码) |
wait()/notify() |
线程间通信机制 |
getClass() |
获取对象的运行时类(反射基础) |
clone() |
对象浅拷贝(需实现Cloneable接口) |
finalize() |
对象回收前的清理(JDK9已废弃) |
重写示例:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return age == user.age && Objects.equals(name, user.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age); // 保证相同对象必有相同哈希值
}
57. 总线锁优缺点
答案:
总线锁机制:
- 作用:CPU通过LOCK#信号锁定总线,保证原子操作
- 应用场景:x86架构的volatile写操作、CAS指令
优点 |
缺点 |
实现简单,严格保证原子性 |
性能差(阻塞所有CPU访问内存) |
适用于少量核心的CPU |
多核环境下成为性能瓶颈 |
现代替代方案:缓存一致性协议(MESI) + 缓存行锁定
58. 多线程顺序输出控制
答案:
使用ReentrantLock+Condition实现:
class PrintABC {
private final Lock lock = new ReentrantLock();
private final Condition[] conditions = new Condition[3];
private int state = 0; // 0:A, 1:B, 2:C
public PrintABC() {
for (int i = 0; i < conditions.length; i++) {
conditions[i] = lock.newCondition();
}
}
public void print(int targetState, String str) {
lock.lock();
try {
while (state % 3 != targetState) {
conditions[targetState].await();
}
System.out.print(str);
state++;
conditions[state % 3].signal();
} finally {
lock.unlock();
}
}
}
// 启动线程
PrintABC printer = new PrintABC();
new Thread(() -> { while(true) printer.print(0, "A"); }).start();
new Thread(() -> { while(true) printer.print(1, "B"); }).start();
new Thread(() -> { while(true) printer.print(2, "C"); }).start();
59. 懒汉式单例模式
答案:
双重检查锁定实现(线程安全):
public class Singleton {
private static volatile Singleton instance; // volatile禁止指令重排序
private Singleton() {} // 私有构造
public static Singleton getInstance() {
if (instance == null) { // 第一次检查
synchronized (Singleton.class) {
if (instance == null) { // 第二次检查
instance = new Singleton();
// 对象初始化分为三步:
// 1. 分配内存 2. 初始化对象 3. 赋值引用
// volatile保证3不会重排序到2之前
}
}
}
return instance;
}
}
类加载时初始化(更简洁的方案):
public class Singleton {
private static class Holder {
static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE; // 类加载时初始化,利用JVM类加载机制保证线程安全
}
}
60. Tomcat访问Controller原理
答案:
完整调用链路(500+字详解):
请求接收阶段
-
- Acceptor线程通过NIO监听端口,将连接注册到Poller线程
- Poller通过Selector检测就绪事件,交给Worker线程池处理
过滤器链处理
// 伪代码展示FilterChain执行
public void doFilter(ServletRequest req, ServletResponse res) {
for (Filter filter : filters) {
filter.doFilter(req, res); // 执行Filter逻辑
}
// 最终到达Servlet
}
Spring MVC集成
-
- DispatcherServlet初始化时创建:
- HandlerMapping(路径到Controller的映射)
- HandlerAdapter(实际调用Controller方法)
- ViewResolver(视图解析)
- DispatcherServlet初始化时创建:
方法调用过程
sequenceDiagram
Tomcat->>DispatcherServlet: service()
DispatcherServlet->>HandlerMapping: getHandler()
HandlerMapping-->>DispatcherServlet: HandlerExecutionChain
DispatcherServlet->>HandlerAdapter: handle()
HandlerAdapter->>Controller: invokeMethod()
Controller-->>HandlerAdapter: ModelAndView
HandlerAdapter-->>DispatcherServlet: 返回结果
DispatcherServlet->>ViewResolver: resolveViewName()
ViewResolver-->>DispatcherServlet: View对象
DispatcherServlet->>View: render()
参数解析机制
-
- HandlerMethodArgumentResolver处理:
- @RequestParam:从URL参数解析
- @RequestBody:使用HttpMessageConverter反序列化
- @PathVariable:从URI模板提取
- HandlerMethodArgumentResolver处理:
响应处理优化
-
- 使用AsyncContext实现异步响应:
@GetMapping("/async")
public void asyncHandle(HttpServletRequest req) {
AsyncContext ctx = req.startAsync();
CompletableFuture.runAsync(() -> {
// 耗时操作
ctx.getResponse().getWriter().write("Done");
ctx.complete();
});
}
性能调优关键点:
- 调整server.tomcat.max-threads(默认200)
- 启用NIO2协议:server.tomcat.protocol=org.apache.coyote.http11.Http11Nio2Protocol
- 合理设置connectionTimeout(通常5000-10000ms)