并发编程-深入解析CAS

并发专题

CAS 无锁机制

  • CAS: Compare and Swap,翻译成比较并交换
  • CAS是一种无锁算法,在不使用锁的情况下实现多线程之间的变量同步
  • CAS 是通过硬件指令,保证原子性
  • CAS 有三个操作数:内存值V,旧的预期值E,要修改的新值N
  • 仅当预期值E和内存值V相同时,将内存值V修改为N
  • V为内存值 (共享变量),E为旧预值(读取的V的副本值、工作内存值),N为新值为可以修改的值
    CAS

CAS应用

CAS 方法说明

  • CAS 操作是由 Unsafe 类提供支持的 native方法
  • CAS 方法接收 4 个参数,分别是:对象实例、内存偏移量、字段期望值、字段新值
@HotSpotIntrinsicCandidate
public final native boolean compareAndSetObject(Object o, long offset,
                                                    Object expected,
                                                    Object x);

@HotSpotIntrinsicCandidate
public final native boolean compareAndSetInt(Object o, long offset,
                                                 int expected,
                                                 int x);

@HotSpotIntrinsicCandidate
public final native boolean compareAndSetLong(Object o, long offset,
                                                  long expected,
                                                  long x);

示例

public static Unsafe getUnsafe() {
		try {
			Field field = Unsafe.class.getDeclaredField("theUnsafe");
			field.setAccessible(true);
			return (Unsafe) field.get(null);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	public static long getFieldOffset(Unsafe unsafe, Class clazz, String fieldName) {
		try {
			return unsafe.objectFieldOffset(clazz.getDeclaredField(fieldName));
		} catch (NoSuchFieldException e) {
			throw new Error(e);
		}
	}

	@Test
	public void casTest(){
		Unsafe unsafe = getUnsafe();
		// 对象实例
		ServerInfo serverInfo = new ServerInfo();
		long offset = getFieldOffset(unsafe, ServerInfo.class, "token");
		// compareAndSwapInt(Object var1, long var2, int var4, int var5)
        // 对象实例、字段的内存偏移量、字段期望值、字段新值
		boolean result = unsafe.compareAndSwapInt(serverInfo, offset, 2, 4);
	}

CAS 源码分析

Hotspot 虚拟机对compareAndSwapInt 方法的实现如下:

#unsafe.cpp
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
  UnsafeWrapper("Unsafe_CompareAndSwapInt");
  oop p = JNIHandles::resolve(obj);
  // 根据偏移量,计算value的地址
  jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
  // Atomic::cmpxchg(x, addr, e) cas逻辑 x:要交换的值   e:要比较的值
  //cas成功,返回期望值e,等于e,此方法返回true 
  //cas失败,返回内存中的value值,不等于e,此方法返回false
  return (jint)(Atomic::cmpxchg(x, addr, e)) == e;

核心逻辑在Atomic::cmpxchg方法中,这个根据不同操作系统和不同CPU会有不同的实现。这里我们以linux_64x的为例,查看Atomic::cmpxchg的实现

#atomic_linux_x86.inline.hpp
inline jint     Atomic::cmpxchg    (jint     exchange_value, volatile jint*     dest, jint     compare_value) {
  //判断当前执行环境是否为多处理器环境
  int mp = os::is_MP();
  //LOCK_IF_MP(%4) 在多处理器环境下,为 cmpxchgl 指令添加 lock 前缀,以达到内存屏障的效果
  //cmpxchgl 指令是包含在 x86 架构及 IA-64 架构中的一个原子条件指令,
  //它会首先比较 dest 指针指向的内存值是否和 compare_value 的值相等,
  //如果相等,则双向交换 dest 与 exchange_value,否则就单方面地将 dest 指向的内存值交给exchange_value。
  //这条指令完成了整个 CAS 操作,因此它也被称为 CAS 指令。
  __asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"
                    : "=a" (exchange_value)
                    : "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
                    : "cc", "memory");
  return exchange_value;
  • Atomic::cmpxchg这个函数最终返回值是exchange_value,也就是说,如果cmpxchgl执行时compare_value和dest指针指向内存值相等则会使得dest指针指向内存值变成exchange_value,最终eax存的compare_value赋值给了exchange_value变量,即函数最终返回的值是原先的compare_value。
  • Unsafe_CompareAndSwapInt的返回值(jint)(Atomic::cmpxchg(x, addr, e)) == e就是true,表明CAS成功

CAS 优缺点

  • 优点:CAS无锁机制,避免用户态到内核态切换
  • 缺点:1、CAS 长时间自旋会消耗CPU资源,开销大 2、ABA问题

ABA问题

如果将原来的值A,改为了B,B有改为了A 发现没有发生变化,实际上已经发生了变化,存在了ABA问题
解决方案:通过版本号码,对每个变量更新的版本就会进行累加,使用原子引用类AtomicStampedReference

public class AtomicStampedReference<V> {

    private static class Pair<T> {
        //  当前值
        final T reference;
        // 版本
        final int stamp;
        private Pair(T reference, int stamp) {
            this.reference = reference;
            this.stamp = stamp;
        }
        static <T> Pair<T> of(T reference, int stamp) {
            return new Pair<T>(reference, stamp);
        }
    }

    private volatile Pair<V> pair;
    ...
}

reference即我们实际存储的变量,stamp是版本,每次修改可以通过+1保证版本唯一性

@Slf4j
public class AtomicStampedReferenceTest {

    private final static AtomicStampedReference<Integer> stamp = new AtomicStampedReference<>(100, 1);

    public static void main(String[] args) {
        new Thread(() -> {
            log.info(Thread.currentThread().getName() + " 第1次版本号:" + stamp.getStamp());
            stamp.compareAndSet(100, 200, stamp.getStamp(), stamp.getStamp() + 1);
            log.info(Thread.currentThread().getName() + " 第2次版本号:" + stamp.getStamp());
            stamp.compareAndSet(200, 100, stamp.getStamp(), stamp.getStamp() + 1);
            log.info(Thread.currentThread().getName() + " 第2次版本号:" + stamp.getStamp());
        }).start();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

janyxe

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值