全面了解Java中的CAS机制
前言
在看到java锁机制的时候,无意中看到了cas这个词,然后在百度查找cas看了很多文章始终没有看的太懂,今天又在google上查找了一些资料,才算是真正弄清楚了cas机制。
什么是cas
在jdk 1.5中增加的一个最主要的支持是atomic类,比如说atomicinteger, atomiclong,这些类可帮助最大限度地减少在多线程中对于一些基本操作(例如,增加或减少多个线程之间共享的值)的复杂性。而这些类的实现都依赖于cas(compare and swap)的算法。
乐观锁和悲观锁
cpu是时分复用的,也就是把cpu的时间片,分配给不同的thread/process轮流执行,时间片与时间片之间,需要进行cpu切换,也就是会发生进程的切换。切换涉及到清空寄存器,缓存数据。然后重新加载新的thread所需数据。当一个线程被挂起时,加入到阻塞队列,在一定的时间或条件下,在通过notify(),notifyall()唤醒回来。在某个资源不可用的时候,就将cpu让出,把当前等待线程切换为阻塞状态。等到资源(比如一个共享数据)可用了,那么就将线程唤醒,让他进入runnable状态等待cpu调度。这就是典型的悲观锁的实现。独占锁是一种悲观锁,synchronized就是一种独占锁,它假设最坏的情况,并且只有在确保其它线程不会造成干扰的情况下执行,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。
但是,由于在进程挂起和恢复执行过程中存在着很大的开销。当一个线程正在等待锁时,它不能做任何事,所以悲观锁有很大的缺点。举个例子,如果一个线程需要某个资源,但是这个资源的占用时间很短,当线程第一次抢占这个资源时,可能这个资源被占用,如果此时挂起这个线程,可能立刻就发现资源可用,然后又需要花费很长的时间重新抢占锁,时间代价就会非常的高。
乐观锁思路就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。某个线程可以不让出cpu,而是一直while循环,如果失败就重试,直到成功为止。所以,当数据争用不严重时,乐观锁效果更好。比如cas就是一种乐观锁思想的应用。
cas(compare and swap )算法
cas中有三个核心参数:
1.主内存中存放的v值,所有线程共享。
2.线程上次从内存中读取的v值a存放在线程的帧栈中,每个线程私有。
3.需要写入内存中并改写v值的b值。也就是线程对a值操作后放入到主存v中。
上面说的比较抽象,看下面的这幅图比较容易理解。
如上图中,主存中保存v值,线程中要使用v值要先从主存中读取v值到线程的工作内存a中,然后计算后变成b值,最后再把b值写回到内存v值中。多个线程共用v值都是如此操作。cas的核心是在将b值写入到v之前要比较a值和v值是否相同,如果不相同证明此时v值已经被其他线程改变,重新将v值赋给a,并重新计算得到b,如果相同,则将b值赋给v。
如果不使用cas机制,看看存在什么问题,假如v=1,现在thread1要对v进行加1,thread2也要对v进行加1,首先thread1读取v=1到自己工作内存a中此时a=1,假设thread2此时也读取v=1到自己的工作内存a中,分别进行加1操作后,两个线程中b的值都为2,此时写回到v中时发现v的值为2,但是两个线程分别对v进行加处理结果却只加了1有问题。
cas核心代码
if (a==v) { v = b return b; } else return v;
上面的操作是原子操作,现在来看看如果两个线程同时要对v进行加1操作使用上面的cas机制后能不能获得正确结果。
①thread 1和thread2 要对v进行加1,thread1和thread2同时读取v值并且对v执行加1操作。
初始值 v=1,a=0, b=0。
②假设thread1,thread 2先读取v值赋给a,并且对a进行加1,得到b=2。
v=1,t1_a=1,t1_b=2;t2_a=1
thread1要将t1_b写入v中,先要执行cas操作:
if (t1_a==v) { v = t1_b return t1_b; } else return v;
因为t1_a=1=v,所以执行 v=t1_b=2,此时v=2。
③thread2也要对v执行加操作。执行加操作之后
v=2 ,t2_a=1,t2_b=2,
当thread2要将t2_b值写要v中之前要执行cas操作,
if (t2_a==v) { v = t2_b return t2_b; } else return v;
此时t2_a=1,v=2, t2_a!=v,这时候返回v=2,给t2_a,t2_a=v=2,然后再次对t2_a进行加1,得到t2_b,此时t2_b=3,t2_a=2,比较t2_a==v,所以将t2_b的值赋给t2_v,t2_v=t2_b=3。最后结果为3。正确。
cas中的aba问题
如果一开始位置v得到的旧值是a,当进行赋值操作时再次读取发现仍然是a,并不能说明变量没有被其它线程改变过。有可能是其它线程将变量改为了b,后来又改回了a。大部分情况下aba问题不会影响程序并发的正确性,如果要解决aba问题,用传统的互斥同步可能比原子类更高效。
aba问题的解决办法
1.在变量前面追加版本号:每次变量更新就把版本号加1,则a-b-a就变成1a-2b-3a。
2.atomic包下的atomicstampedreference类:其compareandset方法首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用的该标志的值设置为给定的更新值。
以上这篇全面了解java中的cas机制就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。
下一篇: PHP编写RESTful接口的方法