java乐观锁是怎么实现的,底层源码看过吗?
1 个回答
谎言不会伤人,真相才是快刀。杜家姐弟# 一、核心原理(一句话) **读-改-写 + CAS(Compare-And-Swap)自旋重试**。不加互斥锁,依赖“版本是否没变”判断来失败重试。 # 二、JDK 里是怎么实现的(源码视角) JDK 乐观锁的典型实现集中在 `java.util.concurrent.atomic` 与 `VarHandle`: 1. **JDK 8(Unsafe 路线)** 以 `AtomicInteger` 为例(简化): ```java public class AtomicInteger implements java.io.Serializable { private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe(); private static final long VALUE = U.objectFieldOffset(AtomicInteger.class.getDeclaredField("value")); private volatile int value; public final int get() { return value; } public final boolean compareAndSet(int expect, int update) { return U.compareAndSwapInt(this, VALUE, expect, update); // 硬件原子指令 } public final int getAndIncrement() { int prev, next; do { prev = get(); next = prev + 1; } while (!compareAndSet(prev, next)); // CAS 失败就自旋重试 return prev; } } ``` 关键点: * `value` 是 `volatile`,保证可见性。 * `Unsafe.compareAndSwapInt` 最终映射到 **CPU 的原子指令**(例如 x86 的 `CMPXCHG`),在 HotSpot 里做成 **intrinsic** 内联。 2. **JDK 9+(VarHandle 路线)** `AtomicInteger` 改用 `VarHandle`(删减示意): ```java private static final VarHandle VALUE; static { VALUE = MethodHandles.lookup().findVarHandle(AtomicInteger.class, "value", int.class); } public final boolean compareAndSet(int expect, int update) { return VALUE.compareAndSet(this, expect, update); // 底层还是走硬件CAS } ``` `VarHandle` 统一了内存语义(acquire/release/opaque 等),但底层仍是 CAS。 3. **ABA 问题的官方方案** * `AtomicStampedReference<T>`:值 + 时间戳(版本号)。 * `AtomicMarkableReference<T>`:值 + 一位标记位。 这两者都是在 CAS 的“期望值”之外,再 CAS 一个“版本/标记”,从而避免 A→B→A 的“看似没变”。 4. **StampedLock 的乐观读** `StampedLock` 提供 `tryOptimisticRead()` 获取“乐观读戳”,读完后 `validate(stamp)` 校验期间是否被写过,失败再退化成读锁。这也是“版本校验”的一种: ```java long stamp = lock.tryOptimisticRead(); // 读共享数据... if (!lock.validate(stamp)) { // 期间有写 stamp = lock.readLock(); try { /* 重新读取 */ } finally { lock.unlockRead(stamp); } } ``` # 三、典型用法(内存对象) 1. **无锁计数/累加** ```java AtomicLong counter = new AtomicLong(); counter.incrementAndGet(); // CAS 自旋 ``` 2. **对象引用更新(不可变对象 + 原子引用)** ```java AtomicReference<Order> ref = new AtomicReference<>(oldOrder); Order newOrder = oldOrder.withStatus(PAID); while (!ref.compareAndSet(oldOrder, newOrder)) { oldOrder = ref.get(); newOrder = oldOrder.withStatus(PAID); } ``` 3. **ABA 需要版本** ```java AtomicStampedReference<Node> head = new AtomicStampedReference<>(null, 0); int[] stamp = new int[1]; Node oldHead = head.get(stamp); Node newHead = pushNode(oldHead); while (!head.compareAndSet(oldHead, newHead, stamp[0], stamp[0] + 1)) { oldHead = head.get(stamp); newHead = pushNode(oldHead); } ``` # 四、数据库层乐观锁(Java 常用实践) 多数业务的“乐观锁”落地在 **DB 的版本号列** 上(MyBatis-Plus/JPA 都内置支持): 1. **SQL 模式** * 表里有 `version` 列(int/bigint)。 * 更新时带上旧版本: ```sql UPDATE t_order SET amount = ?, version = version + 1 WHERE id = ? AND version = ?; ``` * `UPDATE` 影响行数为 1 → 成功;为 0 → 版本冲突,提示重试或失败。 2. **JPA 注解** ```java @Entity class Order { @Id Long id; @Version Long version; // JPA 自动拼 WHERE version = ? } ``` 3. **MyBatis-Plus** ```java @TableName("t_order") class Order { @TableId Long id; @Version Integer version; } ``` 配置版本插件后,`updateById` 会自动做 `WHERE version = ?` 和 `version = version + 1`。 # 五、内存语义 & 性能细节 * **volatile 保证可见性**:CAS 读写的字段通常是 volatile。 * **CAS 是“原子 + 有序写入”**:HotSpot 会在 CAS 前后插入合适的内存屏障,确保指令不会被重排序破坏正确性。 * **自旋开销**:在高争用场景 CAS 可能频繁失败,吞吐下降且耗 CPU,需要**退避策略**(指数退避/Thread.onSpinWait() 等)或改用锁/分段(如 `LongAdder`)。 # 六、常见坑 1. **高冲突导致活锁/饥饿**:尽量降低共享热点(分段累加、批量合并)。 2. **ABA**:链表、栈等无锁结构要特别小心 → `AtomicStampedReference` 或在指针里塞“版本”。 3. **错误的“读-改-写”**:一定要把“读+校验+写”合成一个 CAS 回合;不要“先读,再非原子写”。 4. **长逻辑不要放在自旋里**:只 CAS “指针/标志/版本”,复杂计算先在本地完成。 # 七、什么时候不用乐观锁 * **写冲突概率高**、竞争激烈(例如热点库存扣减、同一行频繁更新)。 * 需要**强顺序、公平性**或**阻塞条件**(队列限流等)。 * **跨资源事务**必须配合更强的一致性手段(如悲观锁/队列/单点串行器)。 # 八、极简对比:乐观 vs 悲观 * 乐观:无锁,失败重试,适合**读多写少、低冲突**。 * 悲观:加锁阻塞,成功率高,适合**写多或强一致顺序**。 # 九、给你一段可复用的重试工具(适用于 DB 乐观锁) ```java public <T> T retry(int max, Supplier<T> attempt, Predicate<Throwable> retryOn) { int i = 0; while (true) { try { return attempt.get(); } catch (Throwable t) { if (++i > max || !retryOn.test(t)) throw t; // 可加指数退避:LockSupport.parkNanos(...) } } } // 用法:更新时捕获“影响行数=0”转成冲突异常,再按需重试 ``` 如果你要看**具体类与方法**: * `AtomicInteger/AtomicLong/AtomicReference#compareAndSet` * JDK8:`sun.misc.Unsafe#compareAndSwapInt/Long/Object` * JDK9+:`java.lang.invoke.VarHandle#compareAndSet / getAndAdd / getOpaque...` * HotSpot:对应平台的 `cmpxchg`/`ll/sc` 等原子指令(由 JVM intrinsic 内联)发布于 2025-08-13 07:40 •IP属地 广东举报评论收藏
公众号进入网站体验更佳

