1. 线程 vs 进程
区别:
- 进程
- 系统资源分配的最小单元
- 独立内存空间,隔离性、安全性高
- 创建/切换开销大
- 一个进程崩溃不会影响其他进程
- 线程
- CPU 调度的最小单元
- 共享进程内存空间,创建/切换开销小
- 安全性较低,易引发共享资源冲突
- 一个线程崩溃可能导致整个进程崩溃
创建线程方式:
- 继承
Thread
- 实现
Runnable
- 实现
Callable
- 线程池
-
CompletableFuture
2. 死锁
出现条件(4 个必要条件):
- 互斥
- 占有并等待
- 不可剥夺
- 循环等待
解决案例:
- 排查定位
- 用
jstack 导出线程堆栈 → 找到 Found one Java-level deadlock
- 分析日志 & 锁获取顺序
- 修复优化
- 统一加锁顺序
-
tryLock(timeout) 防止长时间阻塞
- 数据库事务加锁顺序统一
- 预防措施
- 全局加锁顺序约定文档
- 锁加 TTL
- 开发/测试引入死锁检测工具(Arthas、ThreadMXBean)
3. 乐观锁 vs 悲观锁
- 乐观锁:
不加锁,操作时校验数据是否被修改(版本号/时间戳),冲突时回滚。
- 悲观锁:
操作前直接加锁,保证同一时间只有一个线程操作资源。
4. CAS
- Compare And Swap:比较内存值与期望值,相同则更新,否则自旋重试
- 缺点:
- 高并发下自旋消耗 CPU
- ABA 问题(可用版本号解决)
5. synchronized vs ReentrantLock
synchronized:
- JVM 关键字 →
monitorenter / monitorexit
- 依赖对象监视器锁(Mark Word)
- 锁升级机制:无锁 → 偏向锁 → 轻量级锁 → 重量级锁
- 可重入、非公平、不可中断
- 自动释放锁
- 单一条件队列(
wait/notify)
ReentrantLock:
- API 层锁类,基于 AQS
- 可重入、公平/非公平可选
- 可中断、可超时获取
- 多个条件队列(
Condition)
- 必须手动释放锁(
finally)
6. AQS
- 原理:
- 基于 volatile state + FIFO CLH 队列
- 统一封装线程排队、阻塞、唤醒机制
- 公平锁:获取锁前检查队列(
hasQueuedPredecessors) → 先来先得
- 非公平锁:直接 CAS 抢锁,可能插队
7. synchronized 锁升级流程
- 无锁
- 偏向锁(记录线程 ID,下次同线程直接获得锁)
- 轻量级锁(CAS 自旋)
- 重量级锁(阻塞,OS 互斥量)
可重入锁意义:避免同一线程多次加锁导致自死锁。
8. 线程池
核心参数:
- corePoolSize
- maximumPoolSize
- keepAliveTime & TimeUnit
- workQueue
- threadFactory
- rejectionHandler
任务提交流程:
- 小于核心线程数 → 创建核心线程
- ≥ 核心线程数 → 任务入队
- 队列满 → 创建非核心线程
- 线程数 = 最大值 → 执行拒绝策略
拒绝策略:
-
AbortPolicy(抛异常)
-
DiscardPolicy(直接丢弃)
-
CallerRunsPolicy(调用者线程执行)
-
DiscardOldestPolicy(丢弃队列最老任务)
线程数计算:
- CPU 密集型:
CPU 核数 + 1
- IO 密集型:
CPU 核数 * 2 或 CPU 核数 / (1 - 阻塞系数)
9. 原子类
- 基于 CAS(JDK8 用
Unsafe,JDK9+ 用 VarHandle)
- 无锁高性能,但有 ABA 问题(用带版本号原子类解决)
10. volatile
- 作用:
- 可见性(修改立即对其他线程可见)
- 禁止指令重排序(通过内存屏障)
- 不能保证原子性
11. JMM & happens-before
JMM:线程通过主内存共享变量,每个线程有本地工作内存。
指令重排序:编译器 & CPU 为优化会调整执行顺序(不影响单线程语义)。
happens-before 规则:
- 程序次序
- 锁(解锁 → 加锁)
- volatile(写 → 读)
- 线程启动(
start() → 线程内操作)
- 线程终止(线程内操作 →
join() 返回)
- 传递性