欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

全面了解Java中的CAS机制

程序员文章站 2024-02-26 12:44:28
前言 在看到java锁机制的时候,无意中看到了cas这个词,然后在百度查找cas看了很多文章始终没有看的太懂,今天又在google上查找了一些资料,才算是真正弄清楚了ca...

前言

在看到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中。

上面说的比较抽象,看下面的这幅图比较容易理解。

全面了解Java中的CAS机制

如上图中,主存中保存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机制就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。