Java并发编程之AtomicInteger中的CAS原理分析

1.AtomicInteger简介

java.util.concurrent.atomic.AtomicInteger

这是一个线程安全、具有原子性操作的Integer工具类。

2.AtomicInteger使用

 private static AtomicInteger atomicInteger = new AtomicInteger();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    atomicInteger.incrementAndGet();
                }
            }
        };
        Thread t2 = new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    atomicInteger.incrementAndGet();
                }
            }
        };
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("最终atomicInteger的值:" + atomicInteger.toString());
    }

上述2个线程操作AtomicInteger实现每次自增加1,最终结果为200,由此可见多线程下AtomicInteger是线程安全的。AtomicInteger如何保证线程安全,下一步我们进行源码分析。

3.AtomicInteger线程安全

    private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
   
    private static final long VALUE;

    static {
        try {
            VALUE = U.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (ReflectiveOperationException e) {
            throw new Error(e);
        }
    }

    /**
     * Atomically increments by one the current value.
     *
     * @return the updated value
     */
    public final int incrementAndGet() {
        return U.getAndAddInt(this, VALUE, 1) + 1;
    }

可以看到通过sun.misc.Unsafe实现了自增操作, Unsafe类是在sun.misc包下,不属于Java标准。

/*
 * 反编译查看getAndAddInt
 */
public final int getAndAddInt(Object paramObject, long paramLong, int paramInt)
{
  int i;
  do
  { 
    i = getIntVolatile(paramObject, paramLong);
  } while (!compareAndSwapInt(paramObject, paramLong, i, i + paramInt));
  return i;
}

getIntVolatile和compareAndSwapInt都是native方法

getIntVolatile是获取当前的期望值

compareAndSwapInt就是我们平时说的CAS(compare and swap),通过比较如果内存区的值有没改变,没改变就用新值直接给该内存区赋值,i为期望值即旧值,i + paramInt 为新值。

Unsafe类使Java拥有了像C语言的指针一样操作内存空间的能力,同时也带来了指针的问题。过度的使用Unsafe类会使得出错的几率变大,因此Java官方并不建议使用的,官方文档也几乎没有。 通常我们最好也不要使用Unsafe类,除非有明确的目的,并且也要对它有深入的了解才行。

compareAndSwapInt方法,JDK9命名为compareAndSetInt,最终调用cmpxchg方法

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSetInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x)) {
  oop p = JNIHandles::resolve(obj);
  jint* addr = (jint *)index_oop_from_field_offset_long(p, offset);

  return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
} UNSAFE_END

 cmpxchg方法

汇编指令判断cpu是否多核,多核则进行lock 指令操作 ,lock指令是原子性操作。

lock指令原子性操作锁实现:内存中加缓存行锁 / 值比较大(比如跨几个缓存行)加总线锁

inline jint Atomic::cmpxchg(jint exchange_value,
                            volatile jint* dest,
                            jint compare_value,
                            cmpxchg_memory_order order) {
  int mp = os::is_MP();
  __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;
}

4.原子性

  • 原子性:一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。原子性就像数据库里面的事务一样,他们是一个团队,同生共死。
  • 上述已经分析,CAS操作最终由C++实现,C++最终调用汇编语言中lock指令保证了原子性。
  • 原子性主要是解决了CAS在比较旧值后设置新值前,有新线程修改了当前值的问题。

5.CAS总结

  • CAS无线程阻塞,是非互斥锁,也叫轻量级锁。重度锁会有线程阻塞,需要上下文切换开销,操作系统线程调度开销等问题
  • CAS先与旧值比较,再设置新值,旧值被其他线程修改,则不能设置新值,不断自旋去设置新值
  • CAS原子性操作是由底层lock指令保证,线程安全是通过不断自旋获取锁实现。锁是啥?就是lock呀!

6.CAS缺点

  •  CPU开销过大:在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很大的压力。
  • 不能保证代码块的原子性:CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。

ABA问题:

线程1:A - > B

线程2:在线程1改值前做完A - > C - > A,此时线程1要改值,但是线程1获取到旧值A,并非原来的旧值A

解决办法 : 加版本号,对比版本号不同,则不修改,继续自旋操作。

在JDK中提供了AtomicStampedReference类来解决这个问题,进行compareAndSet的时候,除了要验证数据,还要验证时间戳,如果数据一样,但是时间戳不一样,那么这个数据其实也被修改过了,则进行下一轮自旋操作。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值