cas
cas
使用java.util.concurrent.atomic
包下的AtomicInteger
探索cas
,包下代码符合ocp原则,使用AtomicInteger
进行梳理即可。
分组 | 类 |
---|---|
基础数据型 | AtomicInteger AtomicBoolean AtomicLong |
数组型 | AtomicIntegerArray AtomicLongArray AtomicReferenceArray |
字段更新器 | AtomicIntegerFieldUpdater AtomicLongFieldUpdater AtomicReferenceFieldUpdater |
引用型 | AtomicReference AtomicMarkableReference AtomicStampedReference |
内存布局
查看AtomicInteger
源码,我们会产生疑惑,unsafe,valueOffset
是什么东西?
public class AtomicInteger extends Number implements java.io.Serializable {
// 这个是干什么的呢?不知道
private static final Unsafe unsafe = Unsafe.getUnsafe();
// 这又是什么呢?
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
// 方法暂时忽略
}
使用一段代码来解释unsafe,valueOffset
到底是什么?先引入pom
文件。
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
编写代码输出AtomicInteger
对象内存布局。
public class AtomicIntegerTest {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(0);
System.out.println(ClassLayout.parseInstance(atomicInteger).toPrintable());
}
}
从输出数据中可以看到AtomicInteger.value
在对象布局的OFFSET = 12
处,现在是使用jol-core
进行打印的,如果我们想在代码中使用OFFSET
要怎么使用呢?
java.util.concurrent.atomic.AtomicInteger object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) bc 3d 00 f8 (10111100 00111101 00000000 11111000) (-134201924)
12 4 int AtomicInteger.value 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
debug
上面代码,从图中看到valueOffset=12
,与对象内存布局是一样的,所以这个值在初始化时经被设置到valueOffset
。
基地址+偏移地址
常用方法
unsafe,valueOffset
困惑已经解开,对象存储为一个基地址,在其基地址+偏移量
为需要更新值的内存地址,下面看一下常用方法
unsafe.getAndSetInt
debug代码,发现主要涉及到两个方法调用getIntVolatile
和compareAndSwapInt
。
public final int getAndSetInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var4));
return var5;
}
查看调用的参数,可以看到这里拿到了对象,也就是基地址
,var2就是偏移地址
getIntVolatile
方法的形容如下,支持volatile语意和内存地址值获取,也就是可见性,volatile变量读,总是能看到(任意线程)对这个volatile变量最后的写入。代码注释讲解和上面数据结构布局基本一致,只是对于数组使用基地址 + 类型 * n
也就是B + N * S
,可以参考深入理解计算机系统(原书第 3 版)3.8数组分配和访问
unsafe.compareAndSwapInt
这个方法调用的是native方法,对于方法的形容是支持原子操作,也就是cmpxchg
指令
public final native boolean compareAndSwapInt(Object o,long offset,int expected,int x);
总结
暂时只讲到了cas相关api,底层是直接操作内存地址,使用cmpxchg
指令,关于其他unsafe相关功能,等到用到时在进行解释。