锁本身作为共享资源却能保证线程安全,以及AtomicInteger
等原子类的线程安全实现,其根本原因都依赖于硬件层面的原子操作指令 和内存屏障。下面分层解析其原理:
一、锁如何保证自身线程安全(以 ReentrantLock 为例)
1. 底层依赖:CAS (Compare-And-Swap)
java
// AQS 中的核心 CAS 操作(简化版)
protected final boolean compareAndSetState(int expect, int update) {
// 调用 Unsafe 类的 CAS 原子操作
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
- 硬件支持 :CPU 提供
LOCK CMPXCHG
等原子指令(x86 架构) - 操作原子性:比较-交换操作在一条指令内完成,不会被线程切换打断
2. 锁获取流程(非公平锁)
java
final void lock() {
if (compareAndSetState(0, 1)) // 原子操作:尝试将 state 从 0 改为 1
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
关键保障:
- 多个线程同时执行 CAS 时,只有一个线程 能成功修改
state
值 - 失败线程进入 CLH 队列等待(通过 CAS 保证入队操作的线程安全)
3. 操作系统级保障
当竞争激烈时(重量级锁):
c++
// HotSpot 源码中的锁膨胀(linux 实现)
void ObjectSynchronizer::inflate(Thread* self, oop object) {
...
// 创建 ObjectMonitor 对象
ObjectMonitor* monitor = new ObjectMonitor();
// 通过 CAS 将对象头指向 monitor
if (Atomic::cmpxchg_ptr(monitor, object->mark_addr(), mark) == mark) {
...
}
}
- 最终依赖操作系统提供的 mutex 互斥量 (如 Linux 的
pthread_mutex_t
) - 通过系统调用(如
futex
)实现线程阻塞/唤醒
二、AtomicInteger 的线程安全原理
1. 核心实现(JDK 源码)
java
public class AtomicInteger {
private volatile int value;
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
}
// Unsafe 类中的关键操作
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset); // 读取当前值
} while (!compareAndSwapInt(o, offset, v, v + delta)); // CAS 重试
return v;
}
2. 关键保障机制
机制 | 作用 | 实现层级 |
---|---|---|
volatile 变量 | 保证内存可见性(禁止指令重排序) | JVM 内存屏障 |
CAS 操作 | 保证修改的原子性 | CPU 硬件指令 |
自旋重试 | 处理竞争失败 | Java 代码层 |
三、对比 Integer 的线程不安全
1. Integer 的自增操作
java
Integer count = 0;
count = count + 1; // 非原子操作
实际执行步骤:
- 读取当前值
count
(非原子) - 计算
count+1
(非原子) - 写入新值 (非原子)
2. 多线程下的问题
java
// 线程 A | // 线程 B
read count=0 |
| read count=0
calc 0+1=1 |
| calc 0+1=1
write count=1 |
| write count=1 // 结果应为2,实际为1
四、根本原因对比
特性 | 锁 (ReentrantLock) | 原子类 (AtomicInteger) | Integer |
---|---|---|---|
原子性保障 | CAS+队列管理 | CAS 循环 | 无 |
可见性保障 | AQS 的 volatile state | volatile 变量 | 无 |
竞争处理 | 队列阻塞 | CPU 自旋 | - |
硬件依赖 | CPU 原子指令+OS 系统调用 | CPU 原子指令 | 无 |
适用场景 | 复杂同步逻辑 | 单一变量原子操作 | 只读/线程局部变量 |
五、硬件与操作系统协作示意图
+---------------------+ +---------------------+
| Java 代码层 | | JVM 层 |
| - ReentrantLock |<--->| - AQS 实现 |
| - AtomicInteger | | - 锁膨胀机制 |
+----------↑----------+ +----------↑----------+
| |
+----------↓----------+ +----------↓----------+
| 本地方法库 (JNI) | | 操作系统层 |
| - Unsafe 类 |<--->| - mutex 互斥量 |
| - CAS 操作 | | - futex 快速锁 |
+----------↑----------+ +----------↑----------+
| |
+----------↓----------+ +----------↓----------+
| CPU 层 | | 内存子系统 |
| - LOCK 指令前缀 |<--->| - 缓存一致性协议 |
| - CMPXCHG 指令 | | - 内存屏障 |
+---------------------+ +---------------------+
六、关键结论
-
锁的自身安全根本原因:
- 硬件原子指令(CAS)保证状态修改的原子性
- 内存屏障保证状态可见性
- 操作系统提供阻塞/唤醒原语
-
原子类的线程安全根本原因:
- volatile 保证可见性
- CAS 循环保证原子修改
- 无锁设计避免上下文切换
-
Integer 线程不安全原因:
- 复合操作(读-改-写)不具备原子性
- 缺乏内存可见性保障
💡 设计启示:
- 简单原子操作优先使用原子类(如
AtomicInteger
)- 复杂同步逻辑使用锁(如
ReentrantLock
)- 避免在无同步下修改共享基本类型(如
int
)