介绍
源码分析
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
// setup to use Unsafe.compareAndSwapInt for updates
//JAVA实现CAS算法的类,整个类有关线程安全的操作,都是借助它来实现。
private static final Unsafe unsafe = Unsafe.getUnsafe();
//变量value的内存首地址的偏移量。
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
//存放int值
private volatile int value;
- unsafe:java提供的获得对对象内存地址访问的类,它的作用就是在更新操作时提供“比较并替换”的作用。实际上就是AtomicInteger中的一个工具。
- valueOffset:是用来记录value本身在内存的偏移地址的,这个记录,也主要是为了在更新操作在内存中找到 value 的位置,方便比较。
- value:用来存储整数的实际变量,这里被声明为 volatile,就是为了保证在更新操作时,当前线程可以拿到value 最新的值。
valueOffset 详解
在不了解一个事物的时候,最直接的是先看它的作用,然后参考注释或者其他资料,进一步用自己的理解抽象出一个概念。
作用:是用来记录 value 本身在内存的偏移地址的,这个记录,也主要是为了在更新操作在内存中找到 value 的位置,方便比较。
也就是说,valueOffset 的核心作用是用来定位 value 在 AtomicInteger 对象中的位置的。
进一步了解:
valueOffset 是一个静态常量,并且在类加载时就被赋值了。那么这个类在编译后的字节码是一定的,但是在堆中存放 AtomicInteger 对象的首地址是随机的,所以这里的偏移应该是相对于对象实例的首地址。
再通过下图进行辅助理解:
结论:
- valueOffset 可以定位 AtomicInteger 中 value 的内存地址
- AtomicInteger 中 valueOffset 是固定的,因为 valueOffset 是静态变量,AtomicInteger.class 一加载到 JVM 中 valueOffset 就已经生成了
public class AtomicTest {
public static void main(String[] args) {
AtomicInteger a = new AtomicInteger(1);
AtomicInteger b = new AtomicInteger(100);
int c = a.addAndGet(5);
b.addAndGet(4);
System.out.println(a.get());
System.out.println(c);
}
}
通过 debug 可以发现:AtomicInteger 中 valueOffset 的值为 12
getAndInt
getAndAdd 方法:
/**
* Atomically adds the given value to the current value.
*
* @param delta the value to add
* @return the previous value
*/
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
我们已经知道了 valueOffset 的意义,那么 getAndAdd() 方法也比较好理解了
- this:当前对象
- valueOffSet:value 在 AtomicInteger 实例中的内存偏移地址
- delta:希望被加上的数字
valueOffSet 在这里我们可以理解为 value,因为通过 this 和 valueOffSet,我们就可以得到内存中 value 的值。
unsafe 中的 getAndAddInt 方法:
这里将源码中的参数语意化一点,因为源码中直接看变量名不太好理解。这里再把 native 方法 compareAndSwapInt 一并放进来,并进行语义化注释。
/**
* 比较obj的offset处内存位置中的值和期望的值,如果相同则更新。此更新是不可中断的。
*
* @param obj 需要更新的对象
* @param offset obj中整型field的偏移量
* @param expect 希望field中存在的值
* @param update 如果期望值expect与field的当前值相同,设置filed的值为这个新值
* @return 如果field的值被更改返回true
*/
public native boolean compareAndSwapInt(Object obj, long offset, int expect, int update);
/**
* unsafe.getAndAddInt
*
* @param atomicInstance 需要更新的对象
* @param valueOffSet atomicInstance中 value 的偏移量
* @param delta 希望设置 value 的值为这个新值
* @return 如果field的值被更改返回true
*/
public final int getAndAddInt(Object atomicInstance, long valueOffSet, int delta) {
int result;
do {
result = this.getIntVolatile(atomicInstance, valueOffSet);
} while(!this.compareAndSwapInt(atomicInstance, valueOffSet, result, result + delta));
return result;
}
这里就是 CAS 的核心,它会原子性的完成以下三个操作:
- 获取obj对象中为offset的偏移值,这里假设为realVal
- 比较realVal和expect
- 如果相同,将该值更新为update,否则不更新
语义化后的参数我们可以明白:
- 会去获取内存中 atomicInstance 中 value 的原始值
- 确认原始值没有被其它线程修改时,再执行更新 result + delta 操作,否则会一直循环,等待工作内存中的值与主内存中的值同步
多线程下的 CAS
我们再来对照着源码,走一遍过程,了解一下多线程下 CAS 是如何运作的。
public final int getAndAddInt(Object atomicInstance, long valueOffSet, int delta) {
int result;
do {
//参考 volatile 的作用,此时获取到的是内存中最新的值
result = this.getIntVolatile(atomicInstance, valueOffSet);
//进行CAS对比和更新的原子操作
} while(!this.compareAndSwapInt(atomicInstance, valueOffSet, result, result + delta));
return result;
}
假设现在有两条线程,线程A和线程B在运行AtomicInteger 从1自增到100的过程,假设线程A 先执行。
- 线程A 首先执行 getIntVolatile() 方法,由于 getIntVolatile() 获取的值一定是内存中最新的值,所以值为 1。
- 线程A 下一步需要做CAS操作,假如此时线程A阻塞了(因为getAndAddInt() 并不是原子性的),线程B开始执行。
- 线程B 首先执行 getIntVolatile() 方法,获取到内存中的值同样为1。
- 线程B 进行 CAS 操作,从 atomicInstance 中获取到内存中 value 的值为1,与 result 的值相等,所以进行更新,更新的值为 1 + 1 = 2,此时内存中的值为2。线程B 执行结束。
- 此时线程A 恢复,继续执行,当进行 CAS 操作时,发现 atomicInstance 中获取到内存中 value 的值为2,而线程A中 result 的值为1,两者不相等,所以不进行更新操作,进行循环。
- 下一轮循环,线程A 重新执行 getIntVolatile() 方法,此时获取到的 result 值为 2。
- 此时线程A 执行 CAS 操作,从 atomicInstance 中获取到内存中 value 的值为2,与 result 的值相等,所以进行更新,更新的值为 2 + 1 = 3,此时内存中的值为3。线程A 执行结束。
ps:上述内容有些可能描述不清楚,还望参考以下博客进行理解。如描述有错误,还望留言,我会虚心积极改正,谢谢~
参考:
https://www.imooc.com/article/25026?block_id=tuijian_wz
https://www.cnblogs.com/sharkli/p/5623524.html
https://www.jb51.net/article/136718.htm