最近正在复习Java八股,所以会将一些热门的八股问题,结合ai与自身理解写成博客便于记忆
本文将以上问题为参考解析多线程
一、线程安全核心概念
1. 线程安全定义
线程安全指当多个线程访问某个类时,这个类始终能表现出正确的行为,无需额外的同步协调。线程安全的三个核心要素:
- 原子性:操作不可中断
- 可见性:修改立即对其他线程可见
- 有序性:程序执行顺序符合预期
2. 线程安全实现手段
手段 | 实现方式 | 适用场景 |
---|---|---|
不可变对象 | final修饰 | 配置类、常量类 |
线程封闭 | ThreadLocal | 连接管理、日期格式化 |
同步控制 | synchronized/Lock | 共享资源访问 |
并发容器 | ConcurrentHashMap | 高频读写的集合场景 |
二、synchronized深度解析
1. 实现原理
字节码层面:
// 同步代码块
monitorenter
// 临界区代码
monitorexit
// 同步方法
ACC_SYNCHRONIZED方法标志
JVM实现:
- 每个对象关联一个Monitor(监视器锁)
- 通过对象头中的Mark Word实现锁状态记录
2. 锁升级过程
无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁
升级触发条件:
- 偏向锁:第一个线程访问时,记录线程ID
- 轻量级锁:发生竞争时,通过CAS自旋尝试获取
- 重量级锁:自旋超过阈值(默认10次),升级为OS级互斥锁
对象头结构变化:
锁状态 | 存储内容 |
---|---|
无锁 | 对象哈希码、分代年龄 |
偏向锁 | 持有线程ID、Epoch、分代年龄 |
轻量级锁 | 指向栈中锁记录的指针 |
重量级锁 | 指向互斥量的指针 |
三、CAS机制详解
1. CAS原理
比较并交换(Compare And Swap)的原子操作:
// CPU指令级实现
bool CAS(int* ptr, int expected, int newVal) {
if(*ptr == expected) {
*ptr = newVal;
return true;
}
return false;
}
2. 应用场景
- Atomic类:AtomicInteger等原子类
- 并发容器:ConcurrentHashMap的节点操作
- 自旋锁:ReentrantLock的底层实现
3. 优缺点分析
优势:
- 无锁并发,性能高
- 避免线程上下文切换
缺陷:
- ABA问题(通过版本号解决)
- 自旋消耗CPU
- 只能保证单个变量原子性
四、ReentrantLock对比
1. 核心特性对比
特性 | synchronized | ReentrantLock |
---|---|---|
实现机制 | JVM内置 | JDK实现(AQS) |
锁获取方式 | 自动获取释放 | 必须手动lock/unlock |
可中断性 | 不可中断 | 支持lockInterruptibly() |
公平性 | 非公平 | 可配置公平/非公平 |
条件变量 | 单一wait/notify | 支持多个Condition |
性能 | Java6后优化相当 | 高竞争时表现更好 |
2. 使用示例
ReentrantLock lock = new ReentrantLock(true); // 公平锁
Condition condition = lock.newCondition();
lock.lock();
try {
while(!conditionMet) {
condition.await();
}
// 业务逻辑
condition.signal();
} finally {
lock.unlock();
}
五、AQS框架揭秘
1. AQS核心结构
// 关键属性
volatile int state; // 同步状态
Node head; // 等待队列头节点
Node tail; // 等待队列尾节点
// 节点结构
class Node {
volatile int waitStatus;
volatile Node prev, next;
volatile Thread thread;
Node nextWaiter; // 条件队列专用
}
2. 实现原理
获取锁流程:
- 尝试CAS修改state
- 失败后加入CLH队列
- 进入自旋或阻塞状态
- 被前驱节点唤醒后重新尝试
释放锁流程:
- CAS修改state
- 唤醒后继节点
六、乐观锁与悲观锁
1. 对比分析
维度 | 悲观锁 | 乐观锁 |
---|---|---|
实现方式 | synchronized/ReentrantLock | CAS/版本号 |
适用场景 | 写多读少 | 读多写少 |
并发性能 | 低(阻塞) | 高(无阻塞) |
冲突处理 | 等待 | 回滚重试 |
典型实现 | 数据库行锁 | MVCC/Atomic类 |
2. 数据库乐观锁实现
-- 基于版本号
UPDATE products
SET stock = stock - 1, version = version + 1
WHERE id = 100 AND version = 5;
-- 基于时间戳
UPDATE orders
SET status = 'PAID', update_time = NOW()
WHERE id = 200 AND update_time = '2023-01-01 10:00:00';
七、生产环境实践
1. 锁选择建议
- 低竞争场景:synchronized(简洁高效)
- 高竞争场景:ReentrantLock(灵活控制)
- 读多写少:ReadWriteLock/StampedLock
- 分布式环境:Redis/Zookeeper分布式锁
2. 性能优化技巧
// 减小锁粒度
ConcurrentHashMap<String, Lock> lockMap = new ConcurrentHashMap<>();
Lock lock = lockMap.computeIfAbsent(key, k -> new ReentrantLock());
lock.lock();
try {
// 操作特定key的数据
} finally {
lock.unlock();
}
// 锁分段技术(Java7 ConcurrentHashMap实现)
final Segment<K,V>[] segments;
八、常见问题解答
Q1:synchronized能否保证可见性?
答案:可以。根据JMM规范:
- 解锁前必须把变量写回主内存
- 加锁后必须从主内存读取最新值
Q2:如何解决CAS的ABA问题?
解决方案:
- 使用AtomicStampedReference(带版本号)
- 使用AtomicMarkableReference(带标记位)
AtomicStampedReference<Integer> ref = new AtomicStampedReference<>(100, 0);
ref.compareAndSet(100, 101, stamp, stamp + 1);
Q3:AQS为什么用CLH队列?
设计优势:
- 无锁队列管理(CAS维护)
- 高效唤醒机制(前驱节点负责唤醒)
- 减少缓存同步(局部自旋)