【Java】CAS机制 原子类

本文深入剖析了CAS机制在Java中的应用,特别是在多线程环境下的自增操作。通过AtomicInteger类的具体实现,展示了如何利用CAS避免锁的竞争,提高并发效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

有一类特定的应用,通常是在原有值得基础上做修改,即获取原有值,计算新值,更新。自增就是一个典型的例子。常见的问题比如计数器问题。

在多线程环境下,这个问题当然需要考虑同步,使用syn或者锁肯定可以实现,不过还存在另一种实现方式,CAS。

之前介绍过悲观锁和乐观锁的机制,使用syn锁,是悲观锁,因为要获取锁,然后独占,其余的线程会被阻塞。上述的CAS方式是一种乐观锁机制,本身不需要锁即可同步,而且是非阻塞式的,因为这里没有锁的概念。那么下面就看看它是怎么做到的。

通俗的来讲,乐观锁的实现必然有两个读取,在更新操作刚开始要读取一次,在进行原子更新操作之前又要读取一次,这中间可能会有一些对原有值得计算过程,在更新前,如果检测到两个值不同,说明期间有其他进程更新过,返回执行失败;否则更新成功。这就是大致的流程或者原理。

Java中有针对这种计数器的实现类。主要是concurrent包里面的atomic类。

图片来自别人博客。后面会写。下面以AtomicInteger类来介绍究竟是如何不用syn实现同步更新的。

该类有一个方法getAndIncrement,用于自增。显然这个方法要考虑并发问题。

这里实现的机制叫做CAS,comare and swap。什么意思呢?说白了就是乐观锁。每一次更新前,先拿到当前值expectedValue,然后再计算出更新以后的新值newValue,最后更新,真正的更新操作需要拿到更新变量的地址,看一下这时的值与expectedValue是否一样,一样则更新,否则不更新。这个过程大致是这样的:

 

int expectedValue = get();
//compute new value;
int newValue = compute();
update(expectedValue, newValue);

这只是一个伪代码。真正的更新操作是在update方法里面完成的。这个方法是同步的,而且是硬件级别的同步。事实上,这个方法也必须同步,否则肯定会有并发问题。所以说,CAS并不是没有同步,只不过是没有使用软件的同步,而使用的是硬件级别的同步。这个update方法会拿到变量的地址,从而得到了当前执行时刻变量的值,与expectedValue对比,从而决定是否更新为newValue。

 

Java的unsafe类中的方法提供了CAS的函数接口,底层是调用c语言实现的系统调用。因为unsafe中的方法太底层,因此用户无法直接调用。

下面看源码。

 

   /**
     * Atomically increments by one the current value.
     *
     * @return the previous value
     */
    public final int getAndIncrement() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return current;
        }
    }

这是AtomicInteger的自增实现方法,与前面分析的一样,就是计算期望值和新值,然后做一个系统调用,知道检测到调用成功为止。

 

 

    /**
     * Atomically sets the value to the given updated value
     * if the current value {@code ==} the expected value.
     *
     * @param expect the expected value
     * @param update the new value
     * @return true if successful. False return indicates that
     * the actual value was not equal to the expected value.
     */
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

这就是系统调用,传入this,我猜测是通过反射等机制拿到变量的物理地址,offset指的是实例变量value在对象中物理偏移量,代码如下:

    private static final long valueOffset;

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

expect是原值的大小,update是更新后的大小。

unsafe的compareAndSet函数的执行是硬件同步的,整条语句是原子的,只有更新时,值为expect才会更新成功。底层的CAS指令对应了三个操作数,变量的地址,期望值和新值。最终返回是否执行成功。
效率方面,在中低并发情况下,原子类效果好,但是在高并发下,锁机制更好。
 

 

参考过的博客:http://blog.youkuaiyun.com/zhangerqing/article/details/43057799

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值