CAS:Compare and Swap,即比较再交换。java
jdk5增长了并发包java.util.concurrent.*,其下面的类使用CAS算法实现了区别于synchronouse同步锁的一种乐观锁。JDK 5以前Java语言是靠synchronized关键字保证同步的,这是一种独占锁,也是是悲观锁。算法
对CAS的理解,CAS是一种无锁算法,CAS有3个操做数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改成B,不然什么都不作。编程
CAS比较与交换的伪代码能够表示为:缓存
do{ 备份旧数据; 基于旧数据构造新数据; }while(!CAS( 内存地址,备份的旧数据,新数据 ))
前面说过了,CAS(比较并交换)是CPU指令级的操做,只有一步原子操做,因此很是快。并且CAS避免了请求操做系统来裁定锁的问题,不用麻烦操做系统,直接在CPU内部就搞定了。但CAS就没有开销了吗?不!有cache miss的状况。这个问题比较复杂,首先须要了解CPU的硬件体系结构: 数据结构
上图能够看到一个8核CPU计算机系统,每一个CPU有cache(CPU内部的高速缓存,寄存器),管芯内还带有一个互联模块,使管芯内的两个核能够互相通讯。在图中央的系统互联模块可让四个管芯相互通讯,而且将管芯与主存链接起来。数据以“缓存线”为单位在系统中传输,“缓存线”对应于内存中一个 2 的幂大小的字节块,大小一般为 32 到 256 字节之间。当 CPU 从内存中读取一个变量到它的寄存器中时,必须首先将包含了该变量的缓存线读取到 CPU 高速缓存。一样地,CPU 将寄存器中的一个值存储到内存时,不只必须将包含了该值的缓存线读到 CPU 高速缓存,还必须确保没有其余 CPU 拥有该缓存线的拷贝。并发
好比,若是 CPU0 在对一个变量执行“比较并交换”(CAS)操做,而该变量所在的缓存线在 CPU7 的高速缓存中,就会发生如下通过简化的事件序列:性能
CPU0 检查本地高速缓存,没有找到缓存线。atom
请求被转发到 CPU0 和 CPU1 的互联模块,检查 CPU1 的本地高速缓存,没有找到缓存线。操作系统
请求被转发到系统互联模块,检查其余三个管芯,得知缓存线被 CPU6和 CPU7 所在的管芯持有。code
请求被转发到 CPU6 和 CPU7 的互联模块,检查这两个 CPU 的高速缓存,在 CPU7 的高速缓存中找到缓存线。
CPU7 将缓存线发送给所属的互联模块,而且刷新本身高速缓存中的缓存线。
CPU6 和 CPU7 的互联模块将缓存线发送给系统互联模块。
系统互联模块将缓存线发送给 CPU0 和 CPU1 的互联模块。
CPU0 和 CPU1 的互联模块将缓存线发送给 CPU0 的高速缓存。
CPU0 如今能够对高速缓存中的变量执行 CAS 操做了
以上是刷新不一样CPU缓存的开销。最好状况下的 CAS 操做消耗大概 40 纳秒,超过 60 个时钟周期。这里的“最好状况”是指对某一个变量执行 CAS 操做的 CPU 正好是最后一个操做该变量的CPU,因此对应的缓存线已经在 CPU 的高速缓存中了,相似地,最好状况下的锁操做(一个“round trip 对”包括获取锁和随后的释放锁)消耗超过 60 纳秒,超过 100 个时钟周期。这里的“最好状况”意味着用于表示锁的数据结构已经在获取和释放锁的 CPU 所属的高速缓存中了。锁操做比 CAS 操做更加耗时,是因深刻理解并行编程
为锁操做的数据结构中须要两个原子操做。缓存未命中消耗大概 140 纳秒,超过 200 个时钟周期。须要在存储新值时查询变量的旧值的 CAS 操做,消耗大概 300 纳秒,超过 500 个时钟周期。想一想这个,在执行一次 CAS 操做的时间里,CPU 能够执行 500 条普通指令。这代表了细粒度锁的局限性。
如下是cache miss cas 和lock的性能对比:
在原子类变量中,如java.util.concurrent.atomic中的AtomicXXX,都使用了这些底层的JVM支持为数字类型的引用类型提供一种高效的CAS操做,而在java.util.concurrent中的大多数类在实现时都直接或间接的使用了这些原子变量类。
Java 1.7中AtomicInteger.incrementAndGet()的实现源码为:
因而可知,AtomicInteger.incrementAndGet的实现用了乐观锁技术,调用了类sun.misc.Unsafe库里面的 CAS算法,用CPU指令来实现无锁自增。因此,AtomicInteger.incrementAndGet的自增比用synchronized的锁效率倍增。