Atomic 原子类、Synchronized、ReentrantLock的详细解析

1、Atomic 原子类

1.1 基于 CAS 的实现

Atomic 原子类底层依赖 CAS (Compare-And-Swap) 算法,通过硬件提供的原子指令实现线程安全的更新。CAS 的关键操作包括以下三步:

  1. 比较:将内存中的值和预期值进行比较;
  2. 交换:如果两者相等,则用新值更新内存中的值;
  3. 返回结果:返回是否更新成功。

特点:

  • 无需加锁,避免了线程阻塞,提高性能。
  • 依赖硬件支持的 cmpxchg 指令,在多核 CPU 下性能较好。

CAS 的方法通常在 Java 中用 sun.misc.Unsafe 提供,原子类通过调用 Unsafe 的方法完成内存值的更新。

1.2 Unsafe 类

Atomic 类底层使用了 sun.misc.Unsafe 提供的内存操作方法,这是一种直接操作内存的工具,常见方法包括:

  • compareAndSwapInt:更新 int 类型的变量。
  • compareAndSwapLong:更新 long 类型的变量。
  • compareAndSwapObject:更新引用类型的变量。
1.3 内存偏移量

为了找到变量在内存中的位置,Atomic 类利用 UnsafeobjectFieldOffset 方法获取变量的内存偏移量,通过偏移量直接定位变量位置。

示例(伪代码):

private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;

static {
    try {
        valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
    } catch (Exception ex) {
        throw new Error(ex);
    }
}

public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

2、Synchronized

(具体细节可以看并发编程篇-09-线程安全-synchronized关键字的底层原理-基础回答_哔哩哔哩_bilibili

对象的内存结构

处于不同状态的对象头

2.1 偏向锁

  • 定义:偏向锁是为了优化无竞争场景设计的,当同一个线程多次加锁时,不需要再执行 CAS 操作。
  • 过程
    1. 第一次加锁时,使用CAS操作Mark WordMark Word 的偏向锁标志置为 1,记录当前线程 ID。
    2. 如果是同一个线程重复获取锁,只需检查Mark Word线程 ID,跳过 CAS。
    3. 如果其他线程尝试获取锁,偏向锁撤销并升级为轻量级锁。
  • 特点:适用于无竞争的场景,能显著提高性能。
2.2 轻量级锁

  • 定义:轻量级锁通过 CAS 操作和线程栈中的 Lock Record 来实现锁的快速获取和释放。
  • 过程
    1. 加锁时,线程在栈中创建 Lock Record,并通过 CAS 将对象头的 Mark Word 替换为指向 Lock Record 的指针。
    2. 如果 CAS 成功,则线程持有轻量级锁。
    3. 如果 CAS 失败,说明有其他线程竞争锁,轻量级锁升级为重量级锁。
  • 特点:适用于低竞争的场景,避免了重量级锁的线程阻塞和唤醒开销。
2.3 重量级锁

  • 定义:重量级锁使用 Monitor(监视器)来实现线程的挂起和唤醒,依赖操作系统的互斥锁实现。
  • 过程

当多个线程竞争同一个锁时,Monitor 的作用流程如下:

  1. 线程尝试获取锁:

    • 如果当前锁是空闲状态,线程会成为 Owner(持有锁的线程)。
    • 如果锁已被其他线程持有,当前线程会被挂起,进入 EntryList(阻塞队列),状态变为 BLOCKED
  2. 线程进入等待:

    • 如果线程在持有锁的情况下调用 wait() 方法:
      • 当前线程会释放锁。
      • JVM 会将线程移动到 WaitSet,状态变为 WAITING
      • 等待其他线程调用 notify()notifyAll()
  3. 唤醒和重新竞争:

    • 当其他线程调用 notify()notifyAll() 时,WaitSet 中的线程会被唤醒,重新移动到 EntryList,进入 BLOCKED 状态。
    • EntryList 中选择一个线程,让它获取锁,成为新的 Owner
  4. 锁释放:

    • 当前持有锁的线程执行完同步代码块后,释放锁。
    • 如果 EntryList 中有线程等待,Monitor 会唤醒一个线程并赋予它锁的所有权。
  • 特点:适用于高竞争场景,但上下文切换和线程挂起成本较高。

重量级锁-字节码层面

synchronized 会被编译为字节码指令,具体指令包括:

  • monitorenter:进入 Monitor(加锁)。
  • monitorexit:退出 Monitor(解锁)。

示例代码

public void synchronizedMethod() {
    synchronized (this) {
        System.out.println("Inside synchronized block");
    }
}

对应的字节码

0: aload_0             // 加载 `this` 引用
1: dup                 // 复制引用
2: monitorenter        // 加锁
3: getstatic ...       // 获取 `System.out`
6: ldc "Inside ..."    // 加载字符串
8: invokevirtual ...   // 调用 `println`
11: monitorexit        // 解锁
12: goto 20
15: astore_1           // 异常处理
16: aload_0
17: monitorexit        // 异常时释放锁
18: aload_1
19: athrow
20: return

可以看到:

  • monitorentermonitorexit 实现了加锁和解锁。
  • 异常处理时也确保 monitorexit 执行,避免死锁。

3、ReentrantLock

JAVA八股之源码解析----ReentrantLock原理源码解析-优快云博客

4、对比

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值