JUC: AtomicXXX

使用

JDK在 java.util.concurrent.atomic 下提供了一系列AtomicXX类型,这些类型提供了一系列原子方法,可以避免线程安全问题。下面是AtomicInteger的使用方式:

AtomicInteger i = new AtomicInteger(10);
i.incrementAndGet(); // 多个线程同时增加,不会造成线程安全问题

原理

Atomic类型皆是使用CAS(CompareAndSwap)来实现的。在java中,compareAndSwap也是一个方法,位于UnSafe类中:

// AtomicInteger#incrementAndGet
public final int incrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1; // 给对象this的valueOffset属性+1
}
// UnSafe#getAndAddInt
    public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2); // 获取对象最新的值
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); // 如果不是期望值(var5仍是当前值,说明没有被其他线程修改),返回false,如果是期望值则设置为 var5+1 目标值

        return var5;
    }
// UnSafe#compareAndSet 是一个native方法,如下是他的定义:
// obj :包含要修改field的对象
// offset: obj中整型field的偏移量
// expect: 期待field中存在的值
// update: 如果期待成功,所要更新的新值
public final native boolean compareAndSwapInt(Object obj, long offset, int expect , int update);

CAS需要CPU原语支持,也称为无锁优化,自旋锁。

ABA问题

使用CAS进行无锁优化时,有可能引发ABA问题:

假设现T1线程对变量A进行修改,获取期望值值为1
此时在A未进行CAS操作前,线程T2对变量A已经进行了一系列操作:将变量A变为了2,然后又变为了1
这时线程T1执行CAS操作,期望值为1,目标值也为1,对于T1来说,A是没变的,但是变量A确实已经发生改变了。

变量A不见得是数值,也有可能是对象。对象引用虽然不变,但是成员变量可能已经发生变化。

通俗点说就是,你的前女友在和你复合之后,看似和以前没有区别,但是其实已经经历了n个男人,这就是ABA问题。ABA问题针对简单数字增减一般是不会出现问题,是允许的;但是针对其他对象操作,就有可能发生问题。

解决ABA问题

解决ABA问题最容易想到的办法就是给变量增加版本号,JDK已经了类 AtomicStampedReference 类解决ABA问题:

// stamp:版本号,比如类似的单词timestamp
AtomicStampedReference<Integer> atomicStampedRef = new AtomicStampedReference<Integer>(100, 0);

// 除了比较期望值与目标值,还要比较版本号
// 加一操作
atomicStampedRef.compareAndSet(100, 101, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);
// 减一操作
atomicStampedRef.compareAndSet(101, 100, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);

LongAdder

此外,在 java.util.concurrent.atomic 下还包含XXXAdder类,以LongAdder为例:

LongAdder longAdder = new LongAdder();
longAdder.add(100);
longAdder.increment();

LongAdder采用分段锁,这使得LongAdder的效率高于synchronized、Atomic。

最后更新于