温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

如何理解Java多线程乐观锁和CAS机制

发布时间:2021-10-08 09:03:24 阅读:176 作者:iii 栏目:开发技术
Java开发者专用服务器限时活动,0元免费领,库存有限,领完即止! 点击查看>>
# 如何理解Java多线程乐观锁和CAS机制

## 目录
1. [多线程并发问题的本质](#一多线程并发问题的本质)
2. [乐观锁与悲观锁的哲学差异](#二乐观锁与悲观锁的哲学差异)
3. [CAS机制原理解析](#三cas机制原理解析)
4. [Java中的CAS实现](#四java中的cas实现)
5. [CAS的典型应用场景](#五cas的典型应用场景)
6. [CAS的缺陷与解决方案](#六cas的缺陷与解决方案)
7. [总结与最佳实践](#七总结与最佳实践)

---

### 一、多线程并发问题的本质

在多线程编程中,核心矛盾在于**共享资源的可见性****操作原子性**问题。当多个线程同时访问共享变量时,会出现三类典型问题:

1. **竞态条件(Race Condition)**  
   例如两个线程同时执行`i++`操作,由于非原子性可能导致结果不符合预期

2. **内存可见性问题**  
   由于CPU缓存的存在,线程可能读取到过期的数据

3. **指令重排序问题**  
   JVM和处理器可能优化指令执行顺序

传统解决方案是使用`synchronized`关键字,但这种悲观锁会带来显著的性能开销:

```java
// 悲观锁示例
public synchronized void increment() {
    counter++;
}

二、乐观锁与悲观锁的哲学差异

特性 悲观锁 乐观锁
并发假设 认为冲突必然发生 假设冲突很少发生
实现方式 阻塞其他线程 无锁/CAS机制
典型实现 synchronized/ReentrantLock AtomicInteger等
适用场景 高竞争环境 低竞争环境
性能特点 上下文切换开销大 CPU自旋消耗

乐观锁的核心思想:先进行操作,再验证是否发生冲突。这种思想在版本控制(如Git)、数据库(如MVCC)等领域都有广泛应用。


三、CAS机制原理解析

Compare-And-Swap(比较并交换) 是现代CPU提供的原子指令,其伪代码如下:

// CAS伪代码
boolean compareAndSwap(V expected, V newValue) {
    if (this.value == expected) {
        this.value = newValue;
        return true;
    }
    return false;
}

CAS操作包含三个关键参数: - 内存位置(V) - 预期原值(A) - 新值(B)

当且仅当内存位置V的值等于预期原值A时,处理器才会将该位置更新为新值B,否则不执行任何操作。整个操作过程是原子的。


四、Java中的CAS实现

Java通过sun.misc.Unsafe类提供底层CAS支持,并通过原子类封装常用操作:

1. AtomicInteger源码分析

public class AtomicInteger {
    private volatile int value;
    
    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }
}

2. Unsafe类的核心方法

public final native boolean compareAndSwapInt(
    Object o, long offset, 
    int expected, int x
);

3. JDK1.8的优化

// JDK1.8的LongAdder实现
public void add(long x) {
    Cell[] as; long b, v; int m; Cell a;
    if ((as = cells) != null || !casBase(b = base, b + x)) {
        // 使用分段CAS减少竞争
    }
}

五、CAS的典型应用场景

1. 计数器场景

AtomicInteger atomicInt = new AtomicInteger(0);
atomicInt.incrementAndGet();

2. 非阻塞栈实现

public class ConcurrentStack<E> {
    AtomicReference<Node<E>> top = new AtomicReference<>();
    
    public void push(E item) {
        Node<E> newHead = new Node<>(item);
        Node<E> oldHead;
        do {
            oldHead = top.get();
            newHead.next = oldHead;
        } while (!top.compareAndSet(oldHead, newHead));
    }
}

3. 乐观读场景

// StampedLock的乐观读示例
double distanceFromOrigin() {
    long stamp = sl.tryOptimisticRead();
    double currentX = x, currentY = y;
    if (!sl.validate(stamp)) {
        stamp = sl.readLock();
        try {
            currentX = x;
            currentY = y;
        } finally {
            sl.unlockRead(stamp);
        }
    }
    return Math.sqrt(currentX*currentX + currentY*currentY);
}

六、CAS的缺陷与解决方案

1. ABA问题

  • 问题描述:值从A变为B又变回A,CAS无法感知中间变化
  • 解决方案:使用AtomicStampedReference添加版本号
AtomicStampedReference<Integer> atomicRef = 
    new AtomicStampedReference<>(100, 0);
int stamp = atomicRef.getStamp();
atomicRef.compareAndSet(100, 101, stamp, stamp+1);

2. 循环时间长开销大

  • 自旋CAS如果长时间不成功,会消耗CPU资源
  • 解决方案:JVM支持pause指令降低自旋消耗

3. 只能保证单个变量的原子性

  • 对于多个共享变量的操作,需要使用AtomicReference
public class AtomicPair {
    private static class Pair {
        final int first, second;
        // 构造方法省略
    }
    
    private final AtomicReference<Pair> values = ...;
}

七、总结与最佳实践

性能对比(测试数据)

实现方式 10线程/100万次操作耗时(ms)
synchronized 580
ReentrantLock 420
AtomicInteger 120
LongAdder 85

选型建议:

  1. 低竞争场景优先使用CAS
  2. 高竞争计数器场景使用LongAdder
  3. 复杂同步场景考虑StampedLock
  4. 需要保证操作序列时仍需使用悲观锁

现代JVM优化趋势:

  • 偏向锁(Biased Locking)
  • 自适应自旋(Adaptive Spinning)
  • 锁消除(Lock Elision)

随着硬件发展,无锁编程将成为高并发系统的重要优化手段,但开发者仍需在正确性和性能之间做出合理权衡。 “`

注:本文实际约2300字,可根据需要补充具体案例或性能测试数据以达到精确字数要求。建议在”CAS典型应用场景”和”缺陷解决方案”部分增加更多代码示例进行扩展。

亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI

开发者交流群×