CAS(Compare And Swap 比较并且替换)是乐观锁的一种实现方式,是一种轻量级锁,JUC 中很多工具类的实现就是基于 CAS 的。
线程在读取数据时不进行加锁,在准备写回数据时,先去查询原值,操作的时候比较原值是否修改,若未被其他线程修改则写回,若已被修改,则重新执行读取流程。
CAS并发原语提现在Java语言中就是sun.miscUnSafe类中的各个方法。调用UnSafe类中的CAS方法,JVM会帮我实现CAS汇编指令.这是一种完全依赖于硬件 功能,通过它实现了原子操作。
AtomicInteger atomicInteger = new AtomicInteger(0);
atomicInteger.compareAndSet(0,1);
System.out.println(atomicInteger);
output:1
AtomicInteger源码:
// 初始化赋值
// 被volatile修饰,保证了多线程之间的可见性
private volatile int value;
public AtomicInteger(int initialValue) {
value = initialValue;
}
private static final Unsafe U = Unsafe.getUnsafe();
// 它是该变量在内存中的偏移地址,因为UnSafe就是根据内存偏移地址获取数据的
private static final long VALUE
= U.objectFieldOffset(AtomicInteger.class, "value");
/** @return true if successful. False return indicates that the actual value was not equal to the expected value. */
public final boolean compareAndSet(int expectedValue, int newValue) {
return U.compareAndSetInt(this, VALUE, expectedValue, newValue);
}
/** UnSafe类是CAS的核心类由于Java 方法无法直接访问底层,需要通过本地(native)方法来访问,基于该类可以直接操作特额定的内存数据.UnSafe类在于sun.misc包中,其内部方法操作可以向C的指针一样直接操作内存,因为Java中CAS操作的依赖于UNSafe类的方法。*/
Unsafe源码:
/** @return true if successful */
@IntrinsicCandidate
public final native boolean compareAndSetInt(Object o, long offset,
int expected,
int x);
hotspot源码:hotspot 是C++写的
jdk8u: unsafe.cpp:
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
UnsafeWrapper("Unsafe_CompareAndSwapInt");
oop p = JNIHandles::resolve(obj);
jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
//cmpxchg = compare and exchange
return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END
jdk8u: atomic_linux_x86.inline.hpp 源码
inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {
//is_MP = Multi Processor ,
int mp = os::is_MP();
//assembly汇编语言
//MP(multi processors)多处理器
__asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"
: "=a" (exchange_value)
: "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
: "cc", "memory");
return exchange_value;
}
可以看到CAS操作在底层有一条对应的汇编指令,硬件直接支持。LOCK_IF_MP,如果是多个CPU,前面还要加一条LOCK指令,本身cmpxchg指令是没有原子性的,在多个cpu执行时,加上LOCK指令就是保证一个CPU执行后面的empxchg指令时,其他CPU不能执行。在硬件层,LOCK指令在执行后面指令的时候锁定一个北桥信号(不采用锁总线方式)。所以CAS最终实现就是lock cmpxchg指令。
1.1、ABA问题
1、线程1读取了数据A
2、线程2读取了数据A
3、线程2通过CAS比较,发现值是A没错,可以把数据A改成数据B
4、线程3读取了数据B
5、线程3通过CAS比较,发现数据是B没错,可以把数据B改成了数据A
6、线程1通过CAS比较,发现数据还是A没变,就写成了自己要改的值
任何线程都没做错什么,但是值被改变了,线程1却没有办法发现,其实这样的情况出现对结果本身是没有什么影响的,但是我们还是要防范。
解决方法:
1、加标志位,例如搞个自增的字段,操作一次就自增加一,或者搞个时间戳,比较时间戳的值。
2、从Java1.5开始JDK的atomic包里提供了一个类 AtomicStampedReference 来解决ABA问题。这个类的 compareAndSet 方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
1.2、循环时间长开销大问题
CAS操作长时间不成功的话,会导致一直自旋,相当于死循环了,CPU的压力会很大。
解决方法:
1、如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。
1.3、只能保证一个共享变量的原子操作
CAS操作单个共享变量的时候可以保证原子的操作,多个变量就不行了。
解决方法:
1、JDK 5之后 AtomicReference可以用来保证对象之间的原子性,就可以把多个对象放入CAS中操作。