Java 面试题总结
Java 基础与面向对象
Q1:哪个不属于面向对象特性?
题目: 以下哪个不属于面向对象的特性?
- 复用 ❌(正确答案)
- 封装 ✔
- 多态 ✔
- 继承 ✔
答案: 复用
知识点解析:
面向对象的四大特性:
- 封装(Encapsulation)
- 将数据和方法封装在类中,隐藏内部实现细节
- 通过访问修饰符(private、protected、public)控制访问权限
- 提供公共接口供外部使用
1 | public class BankAccount { |
- 继承(Inheritance)
- 子类继承父类的属性和方法
- 实现代码复用
- 支持方法重写(Override)
1 | class Animal { |
- 多态(Polymorphism)
- 同一接口可以有不同的实现
- 运行时多态(方法重写)
- 编译时多态(方法重载)
1 | // 运行时多态 |
- 抽象(Abstraction)
- 抽象类和接口
- 定义规范,隐藏实现细节
- 通过抽象方法定义契约
1 | // 抽象类 |
扩展知识点:
- 复用(Reusability):是面向对象的优势,但不是特性。通过继承、组合等方式实现代码复用
- 组合 vs 继承:优先使用组合而非继承,提高灵活性
- SOLID 原则:单一职责、开闭原则、里氏替换、接口隔离、依赖倒置
Java 并发与多线程
Q2:Thread 和 Runnable 正确说法?
题目: 关于 Thread 和 Runnable,以下哪个说法正确?
Thread.start()才能开启新线程 ✔(正确答案)Thread.run()可以开启新线程 ❌- 实现 Runnable 接口必须重写 run() 方法 ✔
- Thread 类实现了 Runnable 接口 ✔
答案: Thread.start() 才能开启新线程
知识点解析:
Thread 和 Runnable 的关系:
1 | // Thread 类实现了 Runnable 接口 |
创建线程的方式:
继承 Thread 类
1
2
3
4
5
6
7
8
9class MyThread extends Thread {
public void run() {
System.out.println("Thread running");
}
}
MyThread thread = new MyThread();
thread.start(); // 开启新线程实现 Runnable 接口(推荐)
1
2
3
4
5
6
7
8
9class MyRunnable implements Runnable {
public void run() {
System.out.println("Runnable running");
}
}
Thread thread = new Thread(new MyRunnable());
thread.start(); // 开启新线程实现 Callable 接口(有返回值)
1
2
3
4
5
6
7
8
9
10
11class MyCallable implements Callable<String> {
public String call() throws Exception {
return "Result";
}
}
FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
Thread thread = new Thread(futureTask);
thread.start();
String result = futureTask.get(); // 获取返回值
start() vs run():
start():启动新线程,JVM 调用run()方法run():在当前线程中执行,不会创建新线程
1 | Thread thread = new Thread(() -> System.out.println("Running")); |
Q3:哪个能让线程立刻停止?
题目: 以下哪个方法能让线程立刻停止?
stop()(已废弃,但确实立即终止)✔interrupt()❌suspend()(已废弃)✔yield()❌
答案: stop() 和 suspend()(都已废弃)
知识点解析:
已废弃的方法:
**Thread.stop()**(已废弃)
- 立即终止线程,可能导致数据不一致
- 释放所有锁,可能导致对象状态损坏
**Thread.suspend()**(已废弃)
- 挂起线程,可能导致死锁
- 如果线程持有锁,其他线程无法获取
正确的线程终止方式:
使用中断标志(推荐)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18class MyThread extends Thread {
private volatile boolean running = true;
public void run() {
while (running) {
// 检查中断标志
if (Thread.currentThread().isInterrupted()) {
break;
}
// 执行业务逻辑
}
}
public void stopThread() {
running = false;
}
}使用 interrupt()
1
2
3
4
5
6
7
8
9
10
11
12
13Thread thread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 重新设置中断标志
break;
}
}
});
thread.start();
thread.interrupt(); // 中断线程
Q4:以下语句哪个会导致空指针?
题目: 以下哪个语句可能导致空指针异常?
(s != null) & (s.length() > 0)✔(单 & 不短路)(s != null) && (s.length() > 0)❌(短路与)(s == null) || (s.length() > 0)❌(短路或)
答案: (s != null) & (s.length() > 0)
知识点解析:
逻辑运算符:
- 短路运算符(&&、||)
&&:左边为 false 时,右边不执行||:左边为 true 时,右边不执行
1 | String s = null; |
- 位运算符(&、|)
&:按位与,两边都执行|:按位或,两边都执行
1 | // 位运算符示例 |
Q5:ReentrantLock 默认是?
题目: ReentrantLock 默认是什么锁?
- 公平锁 ❌
- 非公平锁(non-fair)✔(正确答案)
- 可重入锁 ✔
- 不可重入锁 ❌
答案: 非公平锁
知识点解析:
ReentrantLock 的特点:
- 可重入锁(Reentrant)
- 同一线程可以多次获取同一把锁
- 避免死锁
1 | ReentrantLock lock = new ReentrantLock(); |
- 公平锁 vs 非公平锁
1 | // 非公平锁(默认) |
公平锁 vs 非公平锁对比:
| 特性 | 公平锁 | 非公平锁 |
|---|---|---|
| 获取顺序 | 按照等待时间顺序 | 抢占式 |
| 性能 | 吞吐量低 | 吞吐量高 |
| 适用场景 | 需要公平性 | 追求性能 |
公平锁实现原理:
- 使用队列维护等待线程
- 按照 FIFO 顺序获取锁
非公平锁实现原理:
- 新线程可以直接尝试获取锁
- 如果获取失败才进入队列
Q6:ReentrantLock.tryLock() 行为?
题目: ReentrantLock.tryLock() 的行为是什么?
tryLock()不阻塞,立即返回 ✔(正确答案)tryLock()会阻塞等待 ❌tryLock()必须配合超时使用 ❌
答案: tryLock() 不阻塞,立即返回
知识点解析:
ReentrantLock 的获取锁方法:
**lock()**:阻塞等待获取锁
1
2
3
4
5
6
7ReentrantLock lock = new ReentrantLock();
lock.lock(); // 阻塞等待,直到获取锁
try {
// 临界区
} finally {
lock.unlock();
}**tryLock()**:尝试获取锁,不阻塞
1
2
3
4
5
6
7
8
9
10ReentrantLock lock = new ReentrantLock();
if (lock.tryLock()) { // 立即返回 true/false
try {
// 临界区
} finally {
lock.unlock();
}
} else {
// 获取锁失败,执行其他逻辑
}**tryLock(timeout, unit)**:带超时的尝试获取锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14ReentrantLock lock = new ReentrantLock();
try {
if (lock.tryLock(5, TimeUnit.SECONDS)) { // 最多等待 5 秒
try {
// 临界区
} finally {
lock.unlock();
}
} else {
// 超时未获取到锁
}
} catch (InterruptedException e) {
// 处理中断
}
扩展知识点:
synchronized vs ReentrantLock
- synchronized:JVM 层面,自动释放锁
- ReentrantLock:API 层面,需要手动释放,功能更强大
Condition 条件变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
// 等待条件
lock.lock();
try {
while (!conditionMet) {
condition.await(); // 释放锁并等待
}
} finally {
lock.unlock();
}
// 通知条件
lock.lock();
try {
conditionMet = true;
condition.signal(); // 唤醒等待的线程
} finally {
lock.unlock();
}
集合与容器类
Q7:哪个线程安全、且不允许 null?
题目: 以下哪个集合类线程安全且不允许 null 值?
HashMap❌(线程不安全,允许 null)ConcurrentHashMap✔(线程安全,不允许 null)Hashtable✔(线程安全,不允许 null)TreeMap❌(线程不安全,不允许 null)
答案: ConcurrentHashMap 和 Hashtable
知识点解析:
HashMap vs ConcurrentHashMap vs Hashtable:
| 特性 | HashMap | ConcurrentHashMap | Hashtable |
|---|---|---|---|
| 线程安全 | ❌ | ✔ | ✔ |
| 允许 null key | ✔ | ❌ | ❌ |
| 允许 null value | ✔ | ❌ | ❌ |
| 锁粒度 | - | 分段锁/CAS | 表级锁 |
| 性能 | 高 | 较高 | 低 |
| 迭代器 | Fail-Fast | Fail-Safe | Fail-Fast |
ConcurrentHashMap 实现原理:
JDK 1.7:分段锁(Segment)
1
2
3
4// 将数据分成多个段,每个段独立加锁
static final class Segment<K,V> extends ReentrantLock implements Serializable {
// 每个段是一个小的 HashMap
}JDK 1.8+:CAS + synchronized
1
2
3
4
5
6// 使用 CAS 和 synchronized 实现
// 锁粒度更细,性能更好
final V putVal(K key, V value, boolean onlyIfAbsent) {
// 使用 CAS 尝试插入
// 如果失败,使用 synchronized 锁住链表头节点
}
代码示例:
1 | // HashMap(线程不安全) |
扩展知识点:
- CopyOnWriteArrayList:写时复制,适合读多写少
- **Collections.synchronizedMap()**:将普通 Map 包装成线程安全的
- ConcurrentHashMap 的 size() 方法:是近似值,不是精确值
JVM 内存模型
Q9:方法局部变量存放在哪里?
题目: 方法中的局部变量存放在哪里?
- 堆(Heap)❌
- 方法区(Method Area)❌
- 线程独享的栈(Stack)✔(正确答案)
- 程序计数器(PC Register)❌
答案: 线程独享的栈
知识点解析:
JVM 内存区域:
线程共享区域:
- 堆(Heap):对象实例、数组
- 方法区(Method Area):类信息、常量、静态变量
- 元空间(Metaspace):JDK 1.8+,替代永久代
线程私有区域:
- JVM 栈(Stack):局部变量、方法参数、返回值
- 本地方法栈(Native Method Stack):Native 方法
- 程序计数器(PC Register):当前执行的字节码指令地址
栈内存结构:
1 | 栈帧(Stack Frame): |
代码示例:
1 | public class MemoryExample { |
内存分配示例:
1 | public void example() { |
扩展知识点:
- 栈溢出(StackOverflowError):递归调用过深
- 堆溢出(OutOfMemoryError):对象过多,堆内存不足
- 方法区溢出:类加载过多,常量池过大
- 内存泄漏:对象引用未释放,导致无法 GC
Spring & Spring Boot
Q10:@Transactional 不能配置的是什么?
题目: @Transactional 注解不能配置以下哪个选项?
- 事务传播行为(propagation)❌
- 事务隔离级别(isolation)❌
- 异常类注解数组回滚(rollbackFor)❌
- 使用”异常类注解数组”回滚(错误方式)✔
答案: 使用”异常类注解数组”回滚(这是错误的配置方式)
知识点解析:
@Transactional 常用配置:
1 |
|
事务传播行为(Propagation):
| 传播行为 | 说明 |
|---|---|
| REQUIRED(默认) | 如果存在事务则加入,否则创建新事务 |
| REQUIRES_NEW | 总是创建新事务,挂起当前事务 |
| SUPPORTS | 如果存在事务则加入,否则非事务执行 |
| NOT_SUPPORTED | 非事务执行,挂起当前事务 |
| MANDATORY | 必须在事务中,否则抛出异常 |
| NEVER | 不能在事务中,否则抛出异常 |
| NESTED | 嵌套事务 |
代码示例:
1 |
|
Q11:自动配置错误的描述?
题目: 关于 Spring Boot 自动配置,以下哪个描述是错误的?
- 自动配置不会通过
@EnableAutoConfiguration关闭 ❌(错误)✔ - 自动配置基于类路径和条件注解
- 可以通过
spring.autoconfigure.exclude排除自动配置 - 自动配置类通常以
AutoConfiguration结尾
答案: 自动配置不会通过 @EnableAutoConfiguration 关闭(这是错误的描述)
知识点解析:
Spring Boot 自动配置原理:
@EnableAutoConfiguration
1
2
3
4
5
6// 包含 @EnableAutoConfiguration
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}自动配置类示例
1
2
3
4
5
6
public class DataSourceAutoConfiguration {
// 自动配置逻辑
}关闭自动配置的方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 方式1:使用 @SpringBootApplication 的 exclude
public class Application {
}
// 方式2:使用配置文件
// application.yml
spring:
autoconfigure:
exclude:
- org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
// 方式3:使用 @EnableAutoConfiguration 的 exclude
public class Application {
}
自动配置原理:
- 基于类路径:检测类路径中是否存在特定类
- 基于条件注解:@ConditionalOnClass、@ConditionalOnBean 等
- 基于属性配置:@EnableConfigurationProperties
Q12:Bean 依赖顺序,A 在 B 后初始化?
题目: 如何让 BeanA 在 BeanB 之后初始化?
- 在 BeanA 上写
@DependsOn("beanB")✔(正确答案) - 在 BeanB 上写
@DependsOn("beanA")❌ - 使用 @Order 注解 ❌
- 使用 @Primary 注解 ❌
答案: 在 BeanA 上写 @DependsOn("beanB")
知识点解析:
@DependsOn 注解:
1 |
|
Bean 初始化顺序控制:
@DependsOn:明确声明依赖关系
1
2
3
4
// 依赖多个 Bean
public class BeanA {
}@Order:控制 Bean 的加载顺序(主要用于 List 注入)
1
2
3
4
5
6
7
8
// 数字越小,优先级越高
public class BeanA implements Ordered {
public int getOrder() {
return 1;
}
}@Primary:指定主要 Bean(当有多个同类型 Bean 时)
1
2
3
4
5
6
7
8
// 优先注入
public class PrimaryBean implements Service {
}
public class SecondaryBean implements Service {
}
扩展知识点:
- Bean 生命周期:实例化 → 属性注入 → 初始化 → 使用 → 销毁
- @PostConstruct:初始化方法
- @PreDestroy:销毁方法
- InitializingBean 接口:实现 afterPropertiesSet() 方法
设计模式
Q13:用于动态给对象添加功能的是?
题目: 以下哪个设计模式用于动态给对象添加功能?
- 装饰器模式(Decorator)✔(正确答案)
- 代理模式(Proxy)❌
- 适配器模式(Adapter)❌
- 策略模式(Strategy)❌
答案: 装饰器模式
知识点解析:
装饰器模式(Decorator Pattern):
装饰器模式允许向一个现有的对象添加新的功能,同时又不改变其结构。它是作为现有类的一个包装。
1 | // 组件接口 |
Java 中的装饰器模式应用:
java.io包中的流类(InputStream、OutputStream 等)java.util.Collections中的装饰器方法
Q14:代理 vs 装饰
题目: 代理模式和装饰器模式的区别是什么?
答案对比:
| 区别 | 代理模式 | 装饰器模式 |
|---|---|---|
| 目的 | 控制访问 | 增强功能 |
| 关注点 | 控制对象的访问 | 动态添加功能 |
| 是否改变对象行为 | 否(保持原行为) | 是(增强行为) |
| 关系 | 代理类和被代理类实现同一接口 | 装饰器和被装饰类实现同一接口 |
| 使用场景 | 远程代理、虚拟代理、保护代理 | 动态添加功能,如 Java IO 流 |
代理模式示例:
1 | // 接口 |
装饰器模式示例:(见 Q13)
扩展知识点:
- 适配器模式:改变接口,使不兼容的类可以一起工作
- 桥接模式:将抽象与实现分离,使它们可以独立变化
- 外观模式:为子系统提供统一的接口
Q15:状态模式关系?
题目: 状态模式中,上下文(Context)和状态(State)的关系是什么?
- 上下文持有状态对象 ✔(正确答案)
- 状态持有上下文对象 ❌
- 上下文和状态相互独立 ❌
- 状态继承上下文 ❌
答案: 上下文持有状态对象
知识点解析:
状态模式(State Pattern):
允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。
1 | // 状态接口 |
状态模式结构:
- Context(上下文):维护一个 State 实例,定义当前状态
- State(状态接口):定义状态的行为
- ConcreteState(具体状态):实现特定状态的行为
扩展知识点:
- 策略模式:策略可以互换,状态转换有规律
- 状态机:有限状态机(FSM)的实现
- 应用场景:订单状态、游戏角色状态、工作流等
数据库 & SQL
Q16:drop / truncate / delete 对比
题目: 关于 DROP、TRUNCATE、DELETE 的区别,以下哪个说法正确?
delete可加 WHERE ✔(正确答案)truncate可加 WHERE ❌drop可加 WHERE ❌- 三者都可以回滚 ❌
答案: delete 可加 WHERE
知识点解析:
DROP、TRUNCATE、DELETE 对比:
| 特性 | DELETE | TRUNCATE | DROP |
|---|---|---|---|
| 类型 | DML(数据操作语言) | DDL(数据定义语言) | DDL |
| WHERE 子句 | 支持 | 不支持 | 不支持 |
| 回滚 | 可以(事务内) | 不可以(隐式提交) | 不可以 |
| 删除内容 | 删除数据 | 删除所有数据 | 删除表结构 |
| 速度 | 慢(逐行删除) | 快(直接删除数据页) | 最快 |
| 自增 ID | 不重置 | 重置 | 删除表 |
| 触发器 | 触发 | 不触发 | 不触发 |
| 锁 | 行锁 | 表锁 | 表锁 |
代码示例:
1 | -- DELETE:删除指定数据 |
使用场景:
- DELETE:需要删除部分数据,需要回滚
- TRUNCATE:快速清空表,不需要回滚
- DROP:删除整个表结构
Q17:聚簇索引正确的是?
题目: 关于聚簇索引,以下哪个说法正确?
- 可用排序树实现(即 B+Tree)✔(正确答案)
- 只能有一个聚簇索引 ❌(错误:一个表只能有一个聚簇索引,但这是对的)
- 聚簇索引就是主键索引 ❌(不一定)
- 聚簇索引存储的是指针 ❌(存储的是数据本身)
答案: 可用排序树实现(即 B+Tree)
知识点解析:
聚簇索引(Clustered Index):
聚簇索引的索引和数据存储在一起,索引的叶子节点就是数据行。
特点:
- 一个表只能有一个聚簇索引
- 数据按照索引顺序物理存储
- 通常使用 B+Tree 实现
- 主键默认创建聚簇索引
B+Tree 结构:
1 | [根节点] |
聚簇索引 vs 非聚簇索引:
| 特性 | 聚簇索引 | 非聚簇索引 |
|---|---|---|
| 数量 | 一个表只有一个 | 可以有多个 |
| 存储 | 索引和数据在一起 | 索引和数据分离 |
| 叶子节点 | 存储数据行 | 存储指向数据行的指针 |
| 查找速度 | 快(一次查找) | 较慢(两次查找) |
| 实现 | B+Tree | B+Tree |
MySQL InnoDB 中的聚簇索引:
1 | -- 主键自动创建聚簇索引 |
扩展知识点:
- B+Tree vs B-Tree:B+Tree 非叶子节点只存储键值,叶子节点存储数据
- 覆盖索引:查询的列都在索引中,不需要回表
- 索引下推:在索引层面过滤数据,减少回表次数
Q18:SQL Join 哪些正确?
题目: 以下哪些 SQL 连接方式是正确的?
- INNER JOIN 只返回两个表中匹配的行,如果在任何一个表中没有匹配的记录,那么这些行不会出现在结果集中。✔
- LEFT JOIN 返回左表中所有行,即使在右表中没有匹配的行。对于右表中没有匹配的左表行,结果是NULL填充对应的行。✔
- RIGHT JOIN 返回右表中所有行,即使在左表中没有匹配的行。对于左表中没有匹配的右表行,结果是NULL填充对应的行。✔
- UNION ALL 操作符用于合并两个或更多SELECT语句的结果集,它会保留所有行,包括重复的行。如果两个查询结果集中有在所有列上都完全相同的行,UNION ALL 会全部保留,不会去重。
(UNION:会自动去除重复的行(去重)
UNION ALL:保留所有行,包括重复的行(不去重,性能更好))✔ - 所以答案是 4 个都对 ✔
答案: 4 个都对
知识点解析:
SQL JOIN 类型:
INNER JOIN(内连接)
1
2
3
4-- 只返回两个表中匹配的行
SELECT u.name, o.order_id
FROM users u
INNER JOIN orders o ON u.id = o.user_id;LEFT JOIN(左连接)
1
2
3
4
5-- 返回左表所有行,右表匹配的行
SELECT u.name, o.order_id
FROM users u
LEFT JOIN orders o ON u.id = o.user_id;
-- 如果右表没有匹配,返回 NULLRIGHT JOIN(右连接)
1
2
3
4-- 返回右表所有行,左表匹配的行
SELECT u.name, o.order_id
FROM users u
RIGHT JOIN orders o ON u.id = o.user_id;FULL OUTER JOIN(全外连接)
1
2
3
4
5
6
7
8-- MySQL 不支持,可以用 UNION 实现
SELECT u.name, o.order_id
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
UNION
SELECT u.name, o.order_id
FROM users u
RIGHT JOIN orders o ON u.id = o.user_id;UNION / UNION ALL
1
2
3
4
5
6
7
8
9-- UNION:去重
SELECT name FROM users
UNION
SELECT name FROM customers;
-- UNION ALL:不去重(性能更好)
SELECT name FROM users
UNION ALL
SELECT name FROM customers;
JOIN 性能优化:
- 确保 JOIN 条件有索引
- 小表驱动大表
- 避免在 JOIN 条件中使用函数
扩展知识点:
- CROSS JOIN(笛卡尔积):返回两个表的所有组合
- SELF JOIN(自连接):表与自身连接
- 子查询 vs JOIN:JOIN 通常性能更好
HTTP & 网络
Q19:HTTP 状态码正确描述
题目: 关于 HTTP 状态码,以下哪个描述正确?
- 3xx → 重定向,需要进一步操作 ✔(正确答案)
- 2xx → 客户端错误 ❌
- 4xx → 服务器错误 ❌
- 5xx → 成功 ❌
答案: 3xx → 重定向,需要进一步操作
知识点解析:
HTTP 状态码分类:
1xx(信息性状态码):
100 Continue:客户端应继续请求101 Switching Protocols:协议切换
2xx(成功状态码):
200 OK:请求成功201 Created:资源创建成功204 No Content:请求成功,但无内容返回206 Partial Content:部分内容(用于断点续传)
3xx(重定向状态码):
301 Moved Permanently:永久重定向302 Found:临时重定向304 Not Modified:资源未修改(使用缓存)
4xx(客户端错误状态码):
400 Bad Request:请求错误401 Unauthorized:未授权403 Forbidden:禁止访问404 Not Found:资源不存在405 Method Not Allowed:方法不允许
5xx(服务器错误状态码):
500 Internal Server Error:服务器内部错误502 Bad Gateway:网关错误503 Service Unavailable:服务不可用504 Gateway Timeout:网关超时
状态码速记:
- 1xx:提示信息
- 2xx:成功
- 3xx:重定向,需要进一步操作
- 4xx:客户端错误
- 5xx:服务器错误
常见状态码应用场景:
1 | // RESTful API 状态码使用 |
Q20:TCP/UDP 正确描述
题目: 关于 TCP 和 UDP,以下哪个描述正确?
- TCP 与 UDP 都不是面向连接(错误:TCP 是面向连接的)❌
- TCP 丢包后无法”继续传”(要靠重传)❌
- 同端口可同时监听 TCP 和 UDP ✔(正确答案)
- UDP 保证数据有序 ❌
答案: 同端口可同时监听 TCP 和 UDP
知识点解析:
TCP vs UDP 对比:
| 特性 | TCP | UDP |
|---|---|---|
| 连接 | 面向连接(三次握手) | 无连接 |
| 可靠性 | 可靠(确认、重传) | 不可靠 |
| 有序性 | 保证有序 | 不保证有序 |
| 速度 | 较慢 | 较快 |
| 头部开销 | 20 字节 | 8 字节 |
| 流量控制 | 有(滑动窗口) | 无 |
| 拥塞控制 | 有 | 无 |
| 应用场景 | HTTP、FTP、SMTP | DNS、视频流、游戏 |
TCP 三次握手:
1 | 客户端 服务器 |
TCP 四次挥手:
1 | 客户端 服务器 |
端口复用:
1 | // TCP 和 UDP 可以使用相同的端口号 |
TCP 丢包处理:
- TCP 通过确认机制和重传机制保证可靠性
- 发送方发送数据后等待确认(ACK)
- 如果超时未收到确认,会重传数据
- 接收方通过序列号保证数据有序
UDP 特点:
- 无连接:不需要建立连接
- 不可靠:不保证数据到达
- 无状态:不维护连接状态
- 适合实时应用:低延迟
扩展知识点:
- HTTP/1.1 vs HTTP/2:HTTP/2 支持多路复用
- HTTPS:HTTP + TLS/SSL 加密
- WebSocket:全双工通信协议
- QUIC:基于 UDP 的可靠传输协议
线程池 ThreadPoolExecutor
Q21:workQueue 是什么?
题目: ThreadPoolExecutor 中的 workQueue 是什么?
- 阻塞队列 ✔(正确答案)
- 普通队列 ❌
- 同步队列 ❌
- 优先级队列 ❌
答案: 阻塞队列
知识点解析:
ThreadPoolExecutor 核心参数:
1 | ThreadPoolExecutor( |
线程池任务执行流程:
1 | 提交任务 |
工作队列类型:
ArrayBlockingQueue:有界数组队列
1
2
3
4new ThreadPoolExecutor(
5, 10, 60L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100) // 容量为 100
);LinkedBlockingQueue:无界链表队列
1
2
3
4new ThreadPoolExecutor(
5, 10, 60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>() // 无界队列
);SynchronousQueue:同步队列(不存储元素)
1
2
3
4new ThreadPoolExecutor(
5, 10, 60L, TimeUnit.SECONDS,
new SynchronousQueue<>() // 直接提交,不存储
);PriorityBlockingQueue:优先级队列
1
2
3
4new ThreadPoolExecutor(
5, 10, 60L, TimeUnit.SECONDS,
new PriorityBlockingQueue<>() // 按优先级执行
);
拒绝策略:
- AbortPolicy(默认):抛出异常
- CallerRunsPolicy:调用者执行
- DiscardPolicy:直接丢弃
- DiscardOldestPolicy:丢弃最老的任务
线程池使用示例:
1 | // 创建线程池 |
扩展知识点:
- Executors 工具类:提供常用线程池(但不推荐使用)
- 线程池监控:监控线程池状态、任务数量等
- 线程池调优:根据业务场景调整参数
综合算法题解
题:计算日程需要的最少行数(区间重叠)
题目描述: 给定一组时间区间,计算同时进行的最多会议数(即需要的最少会议室数量)。
方法: 小顶堆 + 排序
算法思路:
- 按开始时间排序所有区间
- 使用小顶堆存储当前正在进行的会议结束时间
- 遍历每个区间:
- 如果堆顶的结束时间 <= 当前区间的开始时间,说明会议室已空,移除堆顶
- 将当前区间的结束时间加入堆
- 更新最大堆大小
代码实现:
1 | import java.util.*; |
时间复杂度: O(n log n)
- 排序:O(n log n)
- 遍历 + 堆操作:O(n log n)
空间复杂度: O(n)
- 堆最多存储 n 个元素
扩展题目:
- 合并区间:合并重叠的区间
- 插入区间:在已排序的区间列表中插入新区间
- 无重叠区间:移除最少的区间使剩余区间不重叠
总结
核心知识点回顾
1. Java 基础与面向对象
- 面向对象四大特性:封装、继承、多态、抽象
- 复用是优势,不是特性
- SOLID 原则
2. Java 并发与多线程
- Thread 和 Runnable 的区别
- 正确的线程终止方式(中断标志)
- 逻辑运算符的短路特性
- ReentrantLock:可重入、公平/非公平锁
- tryLock() 不阻塞
3. 集合与容器类
- ConcurrentHashMap:线程安全,不允许 null
- HashMap vs ConcurrentHashMap vs Hashtable
4. JVM 内存模型
- 局部变量存放在栈中
- 线程共享:堆、方法区
- 线程私有:栈、程序计数器、本地方法栈
5. Spring & Spring Boot
- @Transactional 配置
- 自动配置原理和关闭方式
- @DependsOn 控制 Bean 初始化顺序
6. 设计模式
- 装饰器模式:动态添加功能
- 代理模式 vs 装饰器模式
- 状态模式:上下文持有状态对象
7. 数据库 & SQL
- DELETE vs TRUNCATE vs DROP
- 聚簇索引:B+Tree 实现
- SQL JOIN 类型
8. HTTP & 网络
- HTTP 状态码分类
- TCP vs UDP
- 端口复用
9. 线程池
- workQueue 是阻塞队列
- 线程池执行流程
- 拒绝策略
10. 算法
- 区间重叠问题:小顶堆 + 排序
面试重点
- 并发编程:线程创建、同步机制、线程池
- 集合框架:HashMap、ConcurrentHashMap 原理
- JVM:内存模型、垃圾回收
- Spring:IoC、AOP、事务管理
- 设计模式:常用设计模式的应用
- 数据库:索引、事务、SQL 优化
- 网络:HTTP、TCP/IP 协议
学习建议
- 理论结合实践:不仅要理解概念,还要会写代码
- 深入源码:阅读 JDK、Spring 等框架源码
- 多做练习:算法题、项目实战
- 总结归纳:建立知识体系,形成思维导图
- 持续学习:关注新技术,保持学习热情
参考资料:
- 《Java 并发编程实战》
- 《深入理解 Java 虚拟机》
- 《Spring 实战》
- 《设计模式:可复用面向对象软件的基础》
- 《高性能 MySQL》
- 《HTTP 权威指南》