原子变量和原子操作

来源:互联网 发布:win7网络连接有个红叉 编辑:程序博客网 时间:2024/04/28 10:36

原子变量和原子操作

通常情况下,在Java里面,++i或者–i不是线程安全的,这里面有三个独立的操作:获得变量当前值,为该值+1/-1,然后写回新的值。在没有额外资源可以利用的情况下,只能使用加锁才能保证读-改-写这三个操作是“原子性”的。

Java 5新增了AtomicInteger类,该类包含方法getAndIncrement()以及getAndDecrement(),这两个方法实现了原子加以及原子减操作,但是比较不同的是这两个操作没有使用任何加锁机制,属于无锁操作。

CAS操作(Compare and Swap)

CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

现代的CPU提供了特殊的指令,可以自动更新共享数据,而且能够检测到其他线程的干扰,而 compareAndSet() 就用这些代替了锁定。

拿AtomicInteger来研究在没有锁的情况下是如何做到数据正确性的。

private volatile int value;

首先毫无疑问,在没有锁的机制下需要借助volatile原语,保证线程间的数据是可见的(共享的),这样获取变量值的时候才能直接读取。

public final int get() {        return value;    }

然后来看看++i是怎么做到的(jdk1.7)。

public final int incrementAndGet() {    for (;;) {        int current = get();        int next = current + 1;         if (compareAndSet(current, next))            return next;    }}

在这里采用了CAS操作,每次从内存中读取数据然后将此数据和+1后的结果进行CAS操作,如果成功就返回结果,否则重试直到成功为止。而compareAndSet利用JNI来完成CPU指令的操作。

public final boolean compareAndSet(int expect, int update) {       return unsafe.compareAndSwapInt(this, valueOffset, expect, update);    }

综上,我们可以总结出AtomicInteger实现CAS操作的大致思想。首先volatile保证每次get()方法取到的值都是公共栈内的最新值,当然,这并不保证此值接下来不会被其他线程修改。所以要在incrementAndGet()方法中进行检查,检查的关键是compareAndSet()方法,它首先通过this参数和valueOffset参数获得现在在公共栈中的AtomicInteger值,如果和expenct相同,那么说明get()方法取回的值并没有被修改,那么将AtomicInteger的value属性更新为update,并返回true;否则什么也不干并返回false。

valueOffset是一个long参数,代表AtomicInteger参数的value字段相对于此类的偏移量,通过这个参数配合AtomicInteger对象本身,可以找到此时的真实的value字段。

AtomicInteger实现CAS操作的基本原理可以总结为两句话:
1. 尽管AtomicInteger存储的字段可以被其他线程修改,但是存储字段的位置是不变的,这使得我们可以在一个线程内随时得到此类的真实value字段,这是进行CAS检查的基础。
2. 在线程内得到真实值后与原先值进行比较,也就是current字段,如果相同,则说明无其他线程修改这个值,可以进行替换,否则不进行替换。

在此基础上,我们可以试着描述如果在current+1过程中AtomicInteger的value字段被修改了会怎么样?

在jdk1.8中,incrementAndGet直接使用了Unsafe的getAndAddInt方法,而在jdk1.7的Unsafe中,没有此方法。

    public final int incrementAndGet() {        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;    }
原创粉丝点击