Java 中的 CAS 与原子操作
CAS
CAS 全称 Compare And Swap,是一种通过非阻塞的方式实现乐观锁的算法。其原理为:如果待改变的变量V
等于预期值(旧值)E
,就将其设置为新值N
。如果不等于V
,则说明该变量被其他线程更新了,当前线程放弃更新。伪代码如下:
boolean compareAndSwap(var v,var e,var n){
if(v == e){
v = n;
return true;
}
else{
return false;
}
}
CAS是一种原子操作,它是一种系统原语,从CPU层面保证它的原子性,因此不会出现在比较相等后、设置新值前这段时间内变量被其他线程更新。
当变更失败后线程并不会被挂起,仅是被告知失败。因此 CAS 通常搭配while
使用,在失败后不断尝试更新。
Java 实现 CAS —— Unsafe类
sun.misc.Unsafe
类中有一些关于 CAS 的native
方法:
public final native boolean compareAndSwapObject(Object o, long offset,Object expected, Object x);
public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x);
public final native boolean compareAndSwapLong(Object o, long offset,long expected,long x);
参数说明如下:
- o:待改变的对象。
- offset:待改变的属性在内存中与对象的起始内存地址的相对偏移量,通过
o
和offset
参数可以获得待改变的属性值。比如,在此类中就有public native int getIntVolatile(Object o, long offeset)
方法。 - expected:预期值,也就是旧值。
- x:新值。
获取属性对应的相对偏移量的方法如下,以AtomicIntegeer
类中value
属性为例:
private static final long valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));;
一个java对象可以看成是一段内存,各个字段都得按照一定的顺序放在这段内存里,同时考虑到对齐要求,可能这些字段不是连续放置的,用这个方法能准确地告诉你某个字段相对于对象的起始内存地址的字节偏移量,因为是相对偏移量,所以它其实跟某个具体对象又没什么太大关系,跟class的定义和虚拟机的内存模型的实现细节更相关。
Java 中的原子操作
在java.util.concurrent.atomic
包中,JDK 提供了一些原子操作的类,主要为基本类型、数组、引用类型对应的原子类。
下面以AtomicInteger
类为例,看一下 Java 中的原子操作。
AtomicInteger
public class AtomicInteger extends Number implements java.io.Serializable {
// 类中的原子操作通过 Unsafe 对象实现
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); }
}
private volatile int value;
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
}
getAndIncrement()
和getAndAdd(int delta)
方法都是通过 Unsafe
类中的getAndAddInt(Object o, long offset, int delta)
方法实现的:
public final int getAndAddInt(Object o, long offset, int delta) {
int oldValue;
do {
oldValue = this.getIntVolatile(o, offset);
} while(!this.compareAndSwapInt(o, offset, oldValue, oldValue + delta));
return oldValue;
}
public native int getIntVolatile(Object o, long offset);
public final native boolean compareAndSetInt(Object o, long offset,int expected,int x);
getAndAddInt()
方法中使用 do-while 循环,循环条件中调用native
的 CAS 方法,直至成功设置新值,循环体中获取的是旧值,这点从方法名也能看出来,先 get 再 addInt。
上一篇: 1029 Median (25分)
下一篇: 验证MySQL乐观锁与悲观锁并发情况