1. 多线程核心原理与面试要点解析
1.1 线程与进程的本质区别
在操作系统层面,进程是资源分配的基本单位,而线程是CPU调度的基本单位。举个生活中的例子:如果把浏览器看作一个进程,那么它的每个标签页就是一个线程。它们共享浏览器的地址空间(内存资源),但各自独立执行网页渲染任务。
关键差异点:
- 资源占用:进程拥有独立的地址空间,线程共享进程资源
- 创建开销:线程创建比进程快10-100倍(Linux下约10μs vs 1ms)
- 通信成本:进程间通信(IPC)需要内核介入,线程可直接读写共享内存
- 容错性:单个线程崩溃会导致整个进程终止
注意:在Java中直接操作线程属于较底层的做法,实际开发更推荐使用线程池管理
1.2 线程创建的三种方式深度剖析
1.2.1 继承Thread类
java复制class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread ID: " + Thread.currentThread().getId());
}
}
// 使用示例
new MyThread().start();
特点:
- 简单直接,但Java单继承特性限制了扩展性
- 线程与任务强耦合,不符合单一职责原则
1.2.2 实现Runnable接口
java复制class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Running in thread: " + Thread.currentThread().getName());
}
}
// 使用示例
new Thread(new MyRunnable()).start();
优势:
- 任务与执行解耦
- 可复用同一个Runnable对象
- 方便配合线程池使用
1.2.3 实现Callable接口
java复制class PrimeCalculator implements Callable<List<Integer>> {
private final int max;
public PrimeCalculator(int max) {
this.max = max;
}
@Override
public List<Integer> call() {
// 计算质数的实现...
return primes;
}
}
// 使用示例
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<List<Integer>> future = executor.submit(new PrimeCalculator(100));
List<Integer> result = future.get(); // 阻塞获取结果
核心价值:
- 支持返回值(Future模式)
- 允许抛出检查异常
- 配合ExecutorService实现更复杂的异步编程
性能对比:
| 创建方式 | 内存开销 | 创建速度 | 适用场景 |
|---|---|---|---|
| 继承Thread | 较高 | 慢 | 简单测试场景 |
| 实现Runnable | 低 | 快 | 大多数业务场景 |
| 实现Callable | 中等 | 中等 | 需要返回值的任务 |
1.3 线程状态机与生命周期
1.3.1 五种状态详解
-
NEW(新建):
- 刚创建未调用start()
- 线程栈大小默认1MB(可通过-Xss参数调整)
-
RUNNABLE(可运行):
- 包括就绪和运行中两种子状态
- 受线程调度器控制,非确定性切换
-
BLOCKED(阻塞):
- 等待监视器锁(synchronized)
- 不会消耗CPU周期
-
WAITING(等待):
- 无限期等待,需外部唤醒
- 通过Object.wait()或Thread.join()进入
-
TERMINATED(终止):
- run()执行完毕或抛出未捕获异常
- 线程对象可能仍在堆中,但线程已销毁
1.3.2 状态转换触发条件
mermaid复制stateDiagram-v2
[*] --> NEW
NEW --> RUNNABLE: start()
RUNNABLE --> BLOCKED: 等待锁
BLOCKED --> RUNNABLE: 获取锁
RUNNABLE --> WAITING: wait()/join()
WAITING --> RUNNABLE: notify()/线程结束
RUNNABLE --> TERMINATED: run()结束
实测技巧:jstack命令可以抓取线程快照,分析真实应用中的线程状态分布
1.4 线程同步与锁机制
1.4.1 synchronized的底层实现
- 对象头Mark Word:存储锁状态(无锁、偏向锁、轻量级锁、重量级锁)
- 锁升级路径:无锁 → 偏向锁 → 轻量级锁 → 重量级锁
- 锁消除:JIT编译器对不可能存在竞争的锁进行优化
- 锁粗化:将相邻的同步块合并
对象内存布局示例:
code复制|--------------------------------------------------------------|
| Object Header (64 bits) | Instance Data |
|------------------------|----------------|--------------------|
| Mark Word (64 bits) | Klass Pointer | Fields... |
|------------------------|----------------|--------------------|
1.4.2 Lock接口体系
java复制ReentrantLock lock = new ReentrantLock();
try {
lock.lock();
// 临界区代码
} finally {
lock.unlock(); // 必须手动释放
}
与synchronized对比:
| 特性 | synchronized | ReentrantLock |
|---|---|---|
| 获取超时 | 不支持 | tryLock(timeout) |
| 公平锁 | 非公平 | 可配置公平性 |
| 条件变量 | 单一 | 支持多个 |
| 性能(低竞争) | 更优 | 稍差 |
| 代码复杂度 | 简单 | 需要显式释放 |
1.5 线程池深度配置
1.5.1 核心参数详解
java复制ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize, // 常驻线程数
maximumPoolSize, // 最大线程数
keepAliveTime, // 空闲线程存活时间
unit, // 时间单位
workQueue, // 任务队列
threadFactory, // 线程工厂
handler // 拒绝策略
);
队列类型选择:
- SynchronousQueue:直接传递,适合短任务高吞吐
- LinkedBlockingQueue:无界队列,可能导致OOM
- ArrayBlockingQueue:有界队列,需要合理设置大小
- PriorityBlockingQueue:优先级队列
1.5.2 拒绝策略对比
| 策略类 | 行为描述 | 适用场景 |
|---|---|---|
| AbortPolicy | 抛出RejectedExecutionException | 需要明确知道任务被拒绝时 |
| CallerRunsPolicy | 由调用线程执行任务 | 不希望丢失任务的场景 |
| DiscardPolicy | 静默丢弃任务 | 可容忍丢失的非关键任务 |
| DiscardOldestPolicy | 丢弃队列中最老的任务 | 后进任务优先级更高的场景 |
配置建议:
- CPU密集型:corePoolSize = CPU核数 + 1
- IO密集型:corePoolSize = CPU核数 * 2
- 混合型:根据业务特性动态调整
2. 高并发系统设计要点
2.1 并发编程三大问题
2.1.1 可见性问题
java复制// 错误示例
public class VisibilityIssue {
private static boolean flag = true;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
while (flag) {} // 可能永远循环
System.out.println("Thread stopped");
}).start();
Thread.sleep(1000);
flag = false; // 主线程修改
}
}
解决方案:
- volatile关键字
- synchronized块
- Atomic类
2.1.2 原子性问题
java复制// 错误示例
private int counter = 0;
public void unsafeIncrement() {
counter++; // 非原子操作
}
修正方案:
java复制private final AtomicInteger counter = new AtomicInteger(0);
public void safeIncrement() {
counter.incrementAndGet();
}
2.1.3 有序性问题
java复制// 可能发生指令重排
public class Singleton {
private static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton(); // 非原子操作
}
}
}
return instance;
}
}
正确实现(DCL):
java复制private volatile static Singleton instance;
2.2 并发工具类实战
2.2.1 CountDownLatch
java复制// 模拟多任务并行启动
CountDownLatch startLatch = new CountDownLatch(1);
CountDownLatch endLatch = new CountDownLatch(THREAD_COUNT);
for (int i = 0; i < THREAD_COUNT; i++) {
new Thread(() -> {
startLatch.await(); // 等待开始信号
try {
// 执行任务
} finally {
endLatch.countDown();
}
}).start();
}
long start = System.nanoTime();
startLatch.countDown(); // 同时释放所有线程
endLatch.await(); // 等待所有线程完成
long duration = System.nanoTime() - start;
2.2.2 CyclicBarrier
java复制// 模拟多阶段任务
CyclicBarrier barrier = new CyclicBarrier(THREAD_COUNT, () -> {
System.out.println("阶段完成");
});
IntStream.range(0, THREAD_COUNT).forEach(i -> new Thread(() -> {
// 阶段1工作
barrier.await();
// 阶段2工作
barrier.await();
}).start());
3. Spring架构中的并发模式
3.1 Spring的线程模型
3.1.1 Servlet容器线程池
- Tomcat默认配置:
- maxThreads: 200
- acceptCount: 100
- 优化建议:
properties复制server.tomcat.max-threads=500 server.tomcat.min-spare-threads=20
3.1.2 @Async异步处理
java复制@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(50);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("Async-");
executor.initialize();
return executor;
}
}
@Service
public class NotificationService {
@Async
public void sendEmail(String to) {
// 异步发送邮件
}
}
3.2 事务与并发控制
3.2.1 隔离级别问题
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| READ_UNCOMMITTED | 可能 | 可能 | 可能 |
| READ_COMMITTED | 不可能 | 可能 | 可能 |
| REPEATABLE_READ | 不可能 | 不可能 | 可能 |
| SERIALIZABLE | 不可能 | 不可能 | 不可能 |
配置方式:
java复制@Transactional(isolation = Isolation.REPEATABLE_READ)
public void updateAccount(Account account) {
// ...
}
3.2.2 传播行为选择
| 传播行为 | 说明 |
|---|---|
| REQUIRED | 默认,加入当前事务或新建 |
| REQUIRES_NEW | 新建事务,挂起当前事务 |
| NESTED | 嵌套事务(需要JDBC3.0驱动支持) |
| NOT_SUPPORTED | 非事务方式执行 |
3.3 响应式编程模型
3.3.1 WebFlux对比Servlet
| 特性 | Servlet Stack | WebFlux |
|---|---|---|
| 编程模型 | 命令式 | 声明式 |
| 线程模型 | 每个请求占用线程 | 少量线程处理所有请求 |
| 吞吐量 | 较低 | 较高(尤其IO密集型) |
| 学习曲线 | 平缓 | 较陡峭 |
java复制@RestController
@RequestMapping("/api")
public class ReactiveController {
@GetMapping("/users")
public Flux<User> getUsers() {
return userRepository.findAll();
}
@PostMapping("/users")
public Mono<User> createUser(@RequestBody User user) {
return userRepository.save(user);
}
}
4. 面试高频问题精讲
4.1 CAS与ABA问题
CAS实现原理:
java复制public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
ABA问题解决方案:
java复制AtomicStampedReference<Integer> atomicRef =
new AtomicStampedReference<>(100, 0);
int[] stampHolder = new int[1];
int currentValue = atomicRef.get(stampHolder);
int newStamp = stampHolder[0] + 1;
atomicRef.compareAndSet(currentValue, 200, stampHolder[0], newStamp);
4.2 死锁诊断与预防
死锁产生的四个必要条件:
- 互斥条件
- 请求与保持
- 不可剥夺
- 循环等待
诊断工具:
bash复制jstack <pid> | grep -A 10 deadlock
预防策略:
- 锁排序(按固定顺序获取锁)
- 锁超时(tryLock)
- 使用更高级的并发工具(如ConcurrentHashMap)
4.3 并发容器选型指南
| 容器类 | 特性 | 适用场景 |
|---|---|---|
| ConcurrentHashMap | 分段锁/ CAS | 高频读写的键值存储 |
| CopyOnWriteArrayList | 写时复制 | 读多写少的列表 |
| ConcurrentLinkedQueue | 无锁队列 | 高吞吐的生产者消费者场景 |
| BlockingQueue | 阻塞操作 | 线程池任务队列 |
5. 性能优化实战技巧
5.1 线程上下文切换成本
优化方法:
- 减少同步块范围
- 使用线程局部变量(ThreadLocal)
- 避免过度创建线程
测量工具:
bash复制vmstat 1
pidstat -t -p <pid> 1
5.2 锁粒度控制
错误示例:
java复制public synchronized void processOrder(Order order) {
// 包含IO操作等耗时逻辑
}
优化方案:
java复制public void processOrder(Order order) {
// 非同步预处理
synchronized(this) {
// 仅同步核心业务逻辑
}
// 非同步后处理
}
5.3 无锁编程实践
LongAdder vs AtomicLong:
java复制LongAdder adder = new LongAdder();
adder.increment();
// 比AtomicLong在高并发下性能更好
// 原理:分散热点到多个cell
6. 常见陷阱与最佳实践
6.1 线程池使用误区
错误案例:
java复制// 创建无界队列线程池
ExecutorService executor = Executors.newCachedThreadPool();
// 可能导致OOM
正确做法:
java复制ThreadPoolExecutor executor = new ThreadPoolExecutor(
10, 50, 60L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1000),
new ThreadPoolExecutor.CallerRunsPolicy()
);
6.2 ThreadLocal内存泄漏
安全用法:
java复制try {
threadLocal.set(someValue);
// 使用threadLocal
} finally {
threadLocal.remove(); // 必须清理
}
6.3 异步日志记录
推荐方案:
java复制<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="FILE"/>
<queueSize>1024</queueSize>
<discardingThreshold>0</discardingThreshold>
</appender>
7. 前沿技术展望
7.1 协程(虚拟线程)
Java 19+特性:
java复制Thread.startVirtualThread(() -> {
System.out.println("Running in virtual thread");
});
优势:
- 轻量级(内存开销约2KB)
- 高吞吐(可创建数百万个)
- 兼容现有代码
7.2 响应式编程深化
Project Reactor进阶:
java复制Flux.range(1, 10)
.parallel()
.runOn(Schedulers.parallel())
.map(i -> i * 2)
.sequential()
.subscribe(System.out::println);
8. 综合案例分析
8.1 秒杀系统设计
关键技术点:
- 缓存预热(Redis)
- 库存扣减(Lua脚本保证原子性)
- 请求限流(RateLimiter)
- 异步下单(消息队列)
8.2 实时数据聚合
架构方案:
code复制客户端 → Kafka → Flink(窗口计算) → Redis → Dashboard
并发控制:
- 使用Flink的keyBy保证相同key由同一task处理
- 定期checkpoint保证状态一致性
9. 调试与监控体系
9.1 线程转储分析
关键命令:
bash复制jstack <pid> > thread_dump.log
常见问题特征:
- BLOCKED状态线程过多 → 锁竞争激烈
- WAITING状态线程堆积 → 资源不足
9.2 性能监控工具
工具矩阵:
| 工具 | 用途 |
|---|---|
| Arthas | 在线诊断 |
| VisualVM | JVM监控 |
| Prometheus | 指标收集 |
| Grafana | 可视化展示 |
10. 扩展学习路径
10.1 推荐书单
- 《Java并发编程实战》
- 《并发编程的艺术》
- 《深入理解Java虚拟机》
10.2 开源项目
- Netty(高性能网络框架)
- RxJava(响应式编程库)
- Disruptor(无锁队列实现)
在实际项目开发中,我强烈建议从简单的ThreadPoolExecutor开始,逐步过渡到更高级的并发工具。对于Spring应用,合理使用@Async注解可以显著提升IO密集型任务的吞吐量。切记:并发编程的第一原则是保证正确性,其次才是性能优化。