CAS的全称是 Compare And Swap(Compare And Exchange) 比较并交换,
cas(v,a,b) 变量v,期待值a, 修改值b
CAS底层通过Lock指令实现。
以 java.util.concurrent.atomic包下的 AtomicInteger 为例
调用的unsafe.getAndAddInt
getAndAddInt 是一个do..while死循环调用compareAndSwapInt方法
compareAndSwapInt 是一个native方法
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);
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) {
int mp = os::is_MP();
__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;
}
最终是通过LOCK_IF_MP 实现。
什么是aba问题,打个比方, 要修改的变量a的值原本是1,被另一个线程改成了2之后又改成了1,这个时候1还是1,但它已经被修改过了。
举个例子:你的女朋友在和你分开后经历了别的男人又回到你身边怎么办?当然是选择原谅她
解决方法,加版本号,AtomicStampedReference类
CAS是 乐观锁 / 自旋锁 / 轻量级锁 ,之前的synchronized是重量级锁,现在有锁升级过程。
synchronized锁的是对象而不是代码,通过对象头上的两位控制是不是加了锁,加了什么类型的锁。
64位hotspot的实现为
001 表示 无锁态
101 表示偏向锁
00 表示轻量级锁
10 表示重量级锁
11 GC回收标志
1、对象刚创建时是 无锁 或者 匿名偏向锁,
匿名偏向:如果偏向锁打开了就是匿名偏向锁,没打开就是无锁,匿名偏向锁在JVM启动4秒后打开,延迟4秒是因为JVM启动的时候就已经知道哪些对象有竞争,就直接设置为轻量级锁,如果设置成匿名偏向锁还要撤销锁升级成轻量级锁,消耗资源。
2、如果有线程上锁,上偏向锁,偏向锁就是把markword的线程ID改为自己线程ID,下次同一个线程加锁的时候,不需要争用,只需要判断下线程指针是否是同一个,所以,偏向锁,偏向加锁的第一个线程。hashcode备份在线程栈上,线程销毁,锁降级成无锁
3、如果有线程竞争撤销偏向锁升级为轻量级锁(自旋锁),每个线程都有自己的LockRecord在自己的线程栈上,用CAS去争用加锁对象markword的LR的指针,指针指向哪个线程的LR,哪个线程就拥有锁。
4、如果竞争加剧,升级重量级锁,向操作系统申请资源,线程挂起,进入等待队列,等待操作系统的调度。
竞争加剧:JDK1.6之前自旋10次或者自旋线程数超过CPU核心数一般升级重量级锁,JDK1.6加入自适应自旋,由JVM自己控制。
CAS和Synchronized的对比
CAS底层通过LOCK_IF_MP指令实现,在用户态执行,不用经过操作系统老大,效率高。但死循环消耗CPU,所以CAS适合线程低竞争,低耗时的场景。
synchronized升级为重量级锁后线程交给操作系统老大,效率低,但是加入等待队列后不消耗CPU,所以适合高竞争,高耗时的场景。