找回密码
 立即注册
首页 业界区 业界 【锁的本质】锁本身就是共享资源,那么锁如何保证线程安 ...

【锁的本质】锁本身就是共享资源,那么锁如何保证线程安全?

事确 2025-7-11 10:04:16
锁本身作为共享资源却能保证线程安全,以及AtomicInteger等原子类的线程安全实现,其根本原因都依赖于硬件层面的原子操作指令内存屏障。下面分层解析其原理:
一、锁如何保证自身线程安全(以 ReentrantLock 为例)

1. 底层依赖:CAS (Compare-And-Swap)
  1. // AQS 中的核心 CAS 操作(简化版)
  2. protected final boolean compareAndSetState(int expect, int update) {
  3.     // 调用 Unsafe 类的 CAS 原子操作
  4.     return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
  5. }
复制代码

  • 硬件支持:CPU 提供 LOCK CMPXCHG 等原子指令(x86 架构)
  • 操作原子性:比较-交换操作在一条指令内完成,不会被线程切换打断
2. 锁获取流程(非公平锁)
  1. final void lock() {
  2.     if (compareAndSetState(0, 1))  // 原子操作:尝试将 state 从 0 改为 1
  3.         setExclusiveOwnerThread(Thread.currentThread());
  4.     else
  5.         acquire(1);
  6. }
复制代码
关键保障

  • 多个线程同时执行 CAS 时,只有一个线程能成功修改 state 值
  • 失败线程进入 CLH 队列等待(通过 CAS 保证入队操作的线程安全)
3. 操作系统级保障

当竞争激烈时(重量级锁):
  1. // HotSpot 源码中的锁膨胀(linux 实现)
  2. void ObjectSynchronizer::inflate(Thread* self, oop object) {
  3.     ...
  4.     // 创建 ObjectMonitor 对象
  5.     ObjectMonitor* monitor = new ObjectMonitor();
  6.     // 通过 CAS 将对象头指向 monitor
  7.     if (Atomic::cmpxchg_ptr(monitor, object->mark_addr(), mark) == mark) {
  8.         ...
  9.     }
  10. }
复制代码

  • 最终依赖操作系统提供的 mutex 互斥量(如 Linux 的 pthread_mutex_t)
  • 通过系统调用(如 futex)实现线程阻塞/唤醒
二、AtomicInteger 的线程安全原理

1. 核心实现(JDK 源码)
  1. public class AtomicInteger {
  2.     private volatile int value;
  3.    
  4.     public final int incrementAndGet() {
  5.         return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
  6.     }
  7. }
  8. // Unsafe 类中的关键操作
  9. public final int getAndAddInt(Object o, long offset, int delta) {
  10.     int v;
  11.     do {
  12.         v = getIntVolatile(o, offset); // 读取当前值
  13.     } while (!compareAndSwapInt(o, offset, v, v + delta)); // CAS 重试
  14.     return v;
  15. }
复制代码
2. 关键保障机制

机制作用实现层级volatile 变量保证内存可见性(禁止指令重排序)JVM 内存屏障CAS 操作保证修改的原子性CPU 硬件指令自旋重试处理竞争失败Java 代码层三、对比 Integer 的线程不安全

1. Integer 的自增操作
  1. Integer count = 0;
  2. count = count + 1; // 非原子操作
复制代码
实际执行步骤:

  • 读取当前值 count (非原子)
  • 计算 count+1 (非原子)
  • 写入新值 (非原子)
2. 多线程下的问题
  1. // 线程 A          |  // 线程 B
  2. read count=0       |
  3.                    | read count=0
  4. calc 0+1=1         |
  5.                    | calc 0+1=1
  6. write count=1      |
  7.                    | write count=1  // 结果应为2,实际为1
复制代码
四、根本原因对比

特性锁 (ReentrantLock)原子类 (AtomicInteger)Integer原子性保障CAS+队列管理CAS 循环无可见性保障AQS 的 volatile statevolatile 变量无竞争处理队列阻塞CPU 自旋-硬件依赖CPU 原子指令+OS 系统调用CPU 原子指令无适用场景复杂同步逻辑单一变量原子操作只读/线程局部变量五、硬件与操作系统协作示意图
  1. +---------------------+     +---------------------+
  2. |     Java 代码层      |     |       JVM 层         |
  3. |   - ReentrantLock   |<--->| - AQS 实现          |
  4. |   - AtomicInteger   |     | - 锁膨胀机制         |
  5. +----------↑----------+     +----------↑----------+
  6.            |                           |
  7. +----------↓----------+     +----------↓----------+
  8. |  本地方法库 (JNI)   |     |     操作系统层        |
  9. |   - Unsafe 类       |<--->| - mutex 互斥量        |
  10. |   - CAS 操作        |     | - futex 快速锁        |
  11. +----------↑----------+     +----------↑----------+
  12.            |                           |
  13. +----------↓----------+     +----------↓----------+
  14. |        CPU 层       |     |       内存子系统      |
  15. |  - LOCK 指令前缀    |<--->| - 缓存一致性协议      |
  16. |  - CMPXCHG 指令     |     | - 内存屏障           |
  17. +---------------------+     +---------------------+
复制代码
六、关键结论


  • 锁的自身安全根本原因

    • 硬件原子指令(CAS)保证状态修改的原子性
    • 内存屏障保证状态可见性
    • 操作系统提供阻塞/唤醒原语

  • 原子类的线程安全根本原因

    • volatile 保证可见性
    • CAS 循环保证原子修改
    • 无锁设计避免上下文切换

  • Integer 线程不安全原因

    • 复合操作(读-改-写)不具备原子性
    • 缺乏内存可见性保障

<blockquote>

来源:豆瓜网用户自行投稿发布,如果侵权,请联系站长删除

相关推荐

您需要登录后才可以回帖 登录 | 立即注册