java多线程操作之CAS

1,什么是CAS?

CAS(Compare-And-Swap) 比较并交换,用于实现同步和锁机制。经常配合juc中Atomic相关类进行。Atomic相关类无法解决aba问题。


2,CAS核心思想是什么?

比较和交换。
本质上就是乐观锁 + 自旋
这里需要注意的是CAS能保证原子性是介于使用CAS时,比较对象采用了Atomic相关类。如果没有,则不保证原子性。


3,CAS的基本概念

三个操作数
对象: 要比较和可能修改的对象
预期值:当前对象的预期
新值:如果与预期值相等,则更新为新值
两个步骤
比较:比较当前对象是否与预期相等
交换:如果当前对象与预期相等,则交换更新为新值


4,CAS锁的实现

CAS 锁是一种基于 CAS 操作实现的轻量级锁,通常用于实现自旋锁。它的主要优点是避免了传统锁机制的上下文切换开销,提高了并发性能。


4.1 基本实现

package com.rojer.threadCAS;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * 一般情况都配合Atomic类保证其原子性
 */
public class CASExample {
    private AtomicInteger value = new AtomicInteger(0);

    /**
     * compareAndSet(CAS)方法的底层原理基于硬件支持的原子操作,
     * 通过 Unsafe 类封装实现。在现代处理器中,CAS 操作通常通过硬件指令(如 x86 架构上的 cmpxchg 指令)实现。
     * 这些硬件指令能够在不被其他线程打断的情况下完成读取、比较和写入操作,从而实现原子性。
     */
    public void increment() {
        int oldValue, newValue;
        do {
            oldValue = value.get();
            newValue = oldValue + 1;
        } while (!value.compareAndSet(oldValue, newValue));
    }

    public int getValue() {
        return value.get();
    }

    /**
     * 错误的示例
     */
    private Integer value1 = 0;

    public void increment1() {
        // value1++无法保证原子性操作
        value1++;
    }

    public int getValue1() {
        return value1;
    }

    public static void main(String[] args) {
        CASExample example = new CASExample();
        for (int i = 0; i < 1000; i++) {
            new Thread(example::increment).start();
        }
        // 由于多线程的原因,可能需要等待所有线程执行完
        // 这里简单等待
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("AtomicInteger 验证CAS,值为:" + example.getValue());

        for (int i = 0; i < 1000; i++) {
            new Thread(example::increment1).start();
        }

        // 由于多线程的原因,可能需要等待所有线程执行完
        // 这里简单等待
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Integer 验证CAS,值为:" + example.getValue1());
    }
}

执行测试

4.2 当是自定义object为比较对象时

package com.rojer.threadCAS;

import java.util.concurrent.atomic.AtomicStampedReference;

/**
 * 测试CAS比较为一个对象时,避免ABA问题
 */
public class CustomObjectCASExample {

    // 创建测试静态内部类
    private static class CustomObject {
        private final int id;

        public CustomObject(int id) {
            this.id = id;
        }

        public int getId() {
            return id;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            CustomObject that = (CustomObject) o;
            return id == that.id;
        }

        @Override
        public int hashCode() {
            return Integer.hashCode(id);
        }
    }

    // 对象保证原子性使用AtomicStampedReference, 这里第二个参数就是类似版本号,防止ABA问题产生
    private final AtomicStampedReference<CustomObject> reference =
            new AtomicStampedReference<>(new CustomObject(0), 0);

    public void update(int newId) {
        CustomObject oldObject, newObject;
        int oldStamp, newStamp;
        do {
            oldObject = reference.getReference();
            oldStamp = reference.getStamp();
            newObject = new CustomObject(newId);
            newStamp = oldStamp + 1;
        } while (!reference.compareAndSet(oldObject, newObject, oldStamp, newStamp));
    }

    public CustomObject getObject() {
        return reference.getReference();
    }

    public static void main(String[] args) {
        CustomObjectCASExample example = new CustomObjectCASExample();
        for (int i = 0; i < 1000; i++) {
            final int id = i;
            new Thread(() -> example.update(id)).start();
        }
        // 由于多线程的原因,可能需要等待所有线程执行完
        // 这里简单等待
        try { Thread.sleep(1000); } catch (InterruptedException e) { }
        System.out.println(example.getObject().getId());
    }

}

4.3可重入锁的情况 -- 这里已经不是CAS的逻辑了。做额外展示(没有自旋和比较情况下,如何保证原子性)

package com.rojer.threadCAS;

import java.util.concurrent.locks.ReentrantLock;

/**
 * CAS之使用可重入锁
 * 使用可重入锁之后,就不需要自选比较,因为可重入锁也是独占锁
 */
public class ReentrantLockExample {
    private int value = 0;
    private final ReentrantLock lock = new ReentrantLock();

    public void increment() {
        lock.lock();
        try {
            value++;
        } finally {
            lock.unlock();
        }
    }

    public int getValue() {
        return value;
    }

    public static void main(String[] args) {
        ReentrantLockExample example = new ReentrantLockExample();
        for (int i = 0; i < 100; i++) {
            new Thread(example::increment).start();
        }
        // 由于多线程的原因,可能需要等待所有线程执行完
        // 这里简单等待
        try { Thread.sleep(1000); } catch (InterruptedException e) { }
        System.out.println(example.getValue());
    }
}

5, 优点和缺点

优点:
低延迟: 避免传统锁的上下文切换开销
无锁:减少了线程阻塞和唤醒的开销

缺点:
自旋开销: 在高并发场景自旋开销可能过大
适用性: 不适合持有锁的操作,长时间自旋会影响系统性能。
可能导致ABA问题

6,展开1--什么是ABA问题?

1,线程 A 执行 CAS 操作,检查一个值(假设为 A)并希望将其更新为另一个值(假设为 B)。
2, 在 A 线程执行 CAS 操作期间,另一个线程 B 将值从 A 更新为另一个值 C,然后又将其更新回 A。
3, 当线程 A 再次执行 CAS 操作时,它仍然看到值为 A,并认为数据没有被改变,因此成功地将值更新为 B,但实际上,数据在 A 和 B 之间发生了变化。
ABA 问题 的关键在于 CAS 操作无法区分值是否在检查期间被修改和恢复,导致线程 A 在看似安全的操作下可能会做出错误的决策。

7,展开2--为什么Atomic类能保证原子性

atomic类方法底层都采用了Unsafe 类的方法。

Unsafe 类提供了直接操作内存和硬件级别原子操作的方法。compareAndSwapIntUnsafe 类中的一个本地方法(native),通过 JNI 调用底层硬件指令实现原子操作。

  • obj:需要操作的对象。
  • offset:对象中字段的内存偏移量。
  • expected:期望值。
  • x:更新值。

8,展开3--JUC中使用到CAS的地方以ConcurrentHashMap为例

  • 24
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值