CAS?
CAS(Compare And Swap)意为比较并交换,是基于硬件指令实现的同步原语;Unsafe类提供的本地(native)方法中包含了许多实现CAS操作的本地方法,JUC(java.util.concurrent)中许多同步类就是基于此构建的
Unsafe类?
Java无法直接访问底层操作系统,若想访问则需通过本地方法,Unsafe类中提供了许多本地方法,其中包含了CAS操作的本地方法
//参数分别为:目标对象 内存地址偏移量 预期值 交换值
//方法作用为:若对象的内存地址偏移var2处的字段的值与预期值相等 则将该值修改为交换值
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
//获取静态字段的内存地址偏移量
public native long staticFieldOffset(Field var1);
//获取字段的内存地址偏移量
public native long objectFieldOffset(Field var1);
关于内存地址偏移量与内存地址偏移处的字段的值,以原子类AtomicInteger为例:
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;
AtomicInteger的数据储存于value字段,内存地址偏移量储存于valueOffset字段
从静态代码块中不难看出,valueOffset字段储存的内存地址偏移量,即为value字段的内存地址偏移量,显然可知在CAS操作中,value为内存地址偏移处的字段,value的值为被比较并交换的值
CAS存在的问题
资源消耗:线程竞争有时会导致CAS失败,此时CAS失败的线程会进入自旋,直至CAS成功;所以在线程竞争激烈的情况下,CAS会造成大量的资源消耗,影响程序性能
ABA问题:线程A,B对同一变量进行修改,A对于变量的期望值为10;在A对变量进行CAS之前,B将变量的值修改为了11,但又迅速地修改回了10,此时A对变量进行CAS将成功更新变量的值;简而言之,狸猫换太子
问题说明
关于自旋,以AtomicInteger为例:
当前线程通过getIntVolatile方法获取字段的值,储存于var5;但是在执行compareAndSwapInt方法之前,字段的值被修改,导致CAS失败;此时线程将进入自旋,直到CAS成功为止
//自旋锁
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));
return var5;
}
关于ABA问题,示例代码:
public class Test{
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(1);
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("CAS是否成功: " + atomicInteger.compareAndSet(1, 2));
}).start();
new Thread(()->{
atomicInteger.set(2);
System.out.println("atomicInteger被修改为: " + atomicInteger.get());
atomicInteger.set(1);
System.out.println("atomicInteger被修改回: " + atomicInteger.get());
}).start();
}
}
原子引用解决ABA问题
原子引用分为AtomicReference与AtomicStampedReference,区别在于是否带有版本号,前者无法解决ABA问题,但后者可以;换言之,带版本号的原子引用,实际上是乐观锁的一种实现
示例代码:
public class AtomicReferenceDemo {
public static void main(String[] args) {
//AtomicStampedReference<> 带版本号
AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(5, 1);
new Thread(() -> {
int stamped = atomicStampedReference.getStamp();
Integer reference = atomicStampedReference.getReference();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("CAS是否成功: " + atomicStampedReference.compareAndSet(reference, reference + 1, stamped, stamped + 1));
}).start();
new Thread(() -> {
new Thread(() -> {
int stamped = atomicStampedReference.getStamp();
Integer reference = atomicStampedReference.getReference();
atomicStampedReference.compareAndSet(reference, reference + 1, stamped, stamped + 1);
System.out.println("atomicStampedReference被修改为: " + atomicStampedReference.getReference());
int stamped2 = atomicStampedReference.getStamp();
Integer reference2 = atomicStampedReference.getReference();
atomicStampedReference.compareAndSet(reference2, reference2 - 1, stamped2, stamped2 + 1);
System.out.println("atomicStampedReference被修改回: " + atomicStampedReference.getReference());
}).start();
}).start();
}
}