CAS算法和重入锁的实现的简单理解(1)

来源:互联网 发布:mac 无法下载flash 编辑:程序博客网 时间:2024/06/05 16:40
注:本文是在http://www.toutiao.com/i6421671637946466817/?wxshare_count=2&pbid=1498719626基础上整理!
CAS:Compare and Swap,即比较再交换。是无锁操作。
重入锁:java.util.concurrent.ReentrantLock就是使用CAS算法实现的。它是通过算法的
来实现数据操作的互斥性
CAS有3个操作数:内存值V、预期值A、要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。该操作是一个原子操作,被广泛的应用在Java的底层实现中。在Java中,CAS主要是由sun.misc.Unsafe这个类通过JNI调用CPU底层指令实现。
举例:
 

thread1,thread2线程是同时更新同一变量9的值

因为thread1thread2线程都同时去访问同一变量9,所以这两个线程会把主内存的值完全拷贝一份到自己的工作内存空间,所以thread1thread2线程的预期值都为9。假设thread1在与thread2线程竞争中,线程thread1能去更新变量的值,而其他线程都失败。

ps:失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次发起尝试
thread1线程去更新变量值改为10,然后写到内存中。此时对于thread2来说,内存值变为了10,与预期值9不一致,就操作失败了.
通俗解释:CPU去更新一个值,但如果想改的值不再是原来的值,操作就失败,因为很明显,有其它操作先改变了这个值。


CAS开销:
CAS特点:速度快。
原因:1CAS(比较并交换)是CPU指令级的操作,只有一步原子操作,所以非常快。
2且CAS避免了请求操作系统来裁定锁的问题,不用麻烦操作系统,直接在CPU内部就搞定了

CAS的开销在于Cache miss:也就是对某一个变量执行 CAS 操作的 CPU 并不是最后一个操作该变量的那个CPU,二是系统中的其他CPU(假如8核CPU),所以对应的缓存线并不存在于当前对变量执行CAS操作的那个 CPU 的高速缓存中。所以需要将当前变量读取到当前操作CPU的寄存器(高速缓存中)。首先介绍CPU的体系结构:

上图可以看到一个8核CPU计算机系统,每个CPU有cache(CPU内部的高速缓存,寄存器),管芯内还带有一个互联模块(Interconnect),使管芯内的两个核可以互相通信。在图中央的系统互联模块(SystemInterconnect)可以让四个管芯相互通信,并且将管芯与主存(Memory)连接起来。

数据以“缓存线”为单位在系统中传输,“缓存线”对应于内存中一个 2 的幂大小的字节块,大小通常为 32 到 256 字节之间。

当 CPU 从内存中读取一个变量到它的寄存器中时,必须首先将包含了该变量的缓存线读取到 CPU 高速缓存。

同样地,CPU 将寄存器中的一个值存储到内存时,不仅必须将包含了该值的缓存线读到 CPU 高速缓存,还必须确保没有其他 CPU 拥有该缓存线的拷贝。

比如,如果 CPU0 在对一个变量执行“比较并交换”(CAS)操作,而该变量所在的缓存线在 CPU7 的高速缓存中,就会发生以下经过简化的事件序列:

  • CPU0 检查本地高速缓存,没有找到缓存线。

  • 请求被转发到 CPU0 和 CPU1 的互联模块,检查 CPU1 的本地高速缓存,没有找到缓存线。

  • 请求被转发到系统互联模块,检查其他三个管芯,得知缓存线被 CPU6和 CPU7 所在的管芯持有。

  • 请求被转发到 CPU6 和 CPU7 的互联模块,检查这两个 CPU 的高速缓存,在 CPU7 的高速缓存中找到缓存线。

  • CPU7 将缓存线发送给所属的互联模块,并且刷新自己高速缓存中的缓存线。

  • CPU6 和 CPU7 的互联模块将缓存线发送给系统互联模块。

  • 系统互联模块将缓存线发送给 CPU0 和 CPU1 的互联模块。

  • CPU0 和 CPU1 的互联模块将缓存线发送给 CPU0 的高速缓存。

  • CPU0 现在可以对高速缓存中的变量执行 CAS 操作了

所以,就存在这种缓存线迁移的情况,造成CAS开销。