1、CAS简介
jvm的syncronized的重量级锁设计操作系统内核态下互斥锁的使用,线程阻塞和唤醒都涉及到进程在用户态和内核态的频繁切换,导致重量级锁开销大性能低。而syncronized轻量级锁使用CAS进行自旋抢锁,CAS是cpu指令级的原子操作,并处于用户态下,开销较小。
Compare-And-Swap,中文叫做“比较并交换”,它是一种思想、一种算法。
操作系统层面的CAS是一条CPU原子指令(cmpxchg指令),java5所增加的JUC并发包中对操作系统的底层CAS原子操作进行了封装,为上层的java程序提供了CAS操作的API。Unsafe(sun.misc.Unsafe)提供的CAS方法直接通过native方式(封装c++代码)调用了底层的CPU指令cmpxchg。
CAS有三个操作参数:内存值V,预估值A,要修改的值B,当且仅当预期值A和内存值V相同时,才将内存值修改为B,否则什么都不做。CAS 的特点是避免使用互斥锁,当多个线程同时使用 CAS 更新同一个变量时,只有其中一个线程能够操作成功,而其他线程都会更新失败。不过和同步互斥锁不同的是,更新失败的线程并不会被阻塞,而是被告知这次由于竞争而导致的操作失败,但还可以再次尝试。
cmpxchg如何保证多核心下的线程安全?
系统底层进行CAS操作时,会判断当前系统是否为多核心系统,如果是就给总线加锁,只有一个线程会对总线加锁成功,加锁成功后会执行CAS操作,CAS的原子性是平台级别的。
2、Unsafe类介绍
sun.misc.Unsafe是一个final修饰的类,并且构造方法私有
通过反射获取Unsafe对象
public static Unsafe getUnsafeInstance(){
try {
final Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
return (Unsafe) theUnsafe.get(null);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
Unsafe类CAS相关方法:
/**
*@param o 需要操作字段所在对象
*@param offset 需要操作字段相对于对象头的偏移量
*@param expected 期望值(旧值)
*@param x 更新值(新值)
*/
public final boolean compareAndSwapObject(Object o, long offset,Object expected,Object x)
public final boolean compareAndSwapInt(Object o, long offset,int expected,int x)
public final boolean compareAndSwapLong(Object o, long offset,long expected,long x)
使用Unsafe CAS方法:
public class Test {
static Unsafe U;
int count;
static Long countOffset;
static {
try {
final Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
U = (Unsafe) theUnsafe.get(null);
countOffset = U.objectFieldOffset(FieldVisibility.class.getDeclaredField("count"));
} catch (Exception e) {
log.error("Unsafe 初始化异常",e);
}
}
protected final boolean compareAndSetCount(Integer expectedValue, Integer newValue) {
return U.compareAndSwapInt(this, countOffset, expectedValue, newValue);
}
}
3、 原理分析
Unsafe是CAS核心类。Java无法访问底层操作系统,而是通过本地(native)方法访问,Unsafe提供了硬件级别的原子操作。
- AtomicInteger加载Unsafe工具用来直接操作内存
- 用Unsafe来实现底层操作
- 用volatile修饰value字段,保证可见性
- Unsafe类中的compareAndSwapInt方法
- 方法中先想办法拿到变量value在内存中的地址
- 通过Atomic::cmpxchg实现原子性的比较和替换,其中参数x是即将更新的值,参数e是原内存的值。
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
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;
}
4、CAS的缺点
- ABA问题
线程1将结果改为B线程2将结果改为A,让线程误以为没有线程对数据进行操作
解决方案: 加版本号,使用AtomicStampedReference
public class AtomicStampedReference<V> {
private static class Pair<T> {
final T reference;
/**版本号*/
final int stamp;
private Pair(T reference, int stamp) {
this.reference = reference;
this.stamp = stamp;
}
static <T> Pair<T> of(T reference, int stamp) {
return new Pair<T>(reference, stamp);
}
}
private volatile Pair<V> pair;
/**
*expectedReference 期望引用(旧值)
*newReference 新引用(旧值)
*expectedStamp 期望版本号(旧值)
*newStamp 新版本号
*/
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
Pair<V> current = pair;
return
expectedReference == current.reference &&
expectedStamp == current.stamp &&
((newReference == current.reference &&
newStamp == current.stamp) ||
casPair(current, Pair.of(newReference, newStamp)));
}
}
- 高并发时的性能问题,自旋时间比较长
由于单次 CAS 不一定能执行成功,所以CAS 往往是配合着循环来实现的,有的时候甚至是死循环,不停地进行重试,直到线程竞争不激烈的时候,才能修改成功。如果应用场景本身就是高并发的场景,就有可能导致 CAS 一直都操作不成功,循环时间就会越来越长。在此期间,CPU 资源也是一直在被消耗,这会对性能产生很大的影响。
- 范围不能灵活控制
不能针对多个共享变量同时进行 CAS 操作,因为这多个变量之间是独立的,简单的把原子操作组合到一起,并不具 备原子性。因此如果我们想对多个对象同时进行 CAS 操作并想保证线程安全的话,是比较困难的