全面了解Java中的CAS机制

 更新时间:2017年10月09日 09:53:44   作者:HankingHu  
下面小编就为大家带来一篇全面了解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机制就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Java中上传图片压缩处理的方法示例

    Java中上传图片压缩处理的方法示例

    本篇文章主要介绍了Java中图片压缩处理的方法示例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-02-02
  • Java Spring分别实现定时任务方法

    Java Spring分别实现定时任务方法

    这篇文章主要为大家详细介绍了Java与Spring设置动态定时任务的方法,定时任务的应用场景十分广泛,如定时清理文件、定时生成报表、定时数据同步备份等
    2022-07-07
  • 详解MyBatis-Puls中saveBatch批量添加慢的问题

    详解MyBatis-Puls中saveBatch批量添加慢的问题

    本文主要介绍了详解MyBatis-Puls中saveBatch批量添加慢的问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-01-01
  • 教你怎么用Java实现给图片打上水印

    教你怎么用Java实现给图片打上水印

    这篇文章主要介绍了教你怎么用Java实现给图片打上水印,文中有非常详细的代码示例,对正在学习java的小伙伴们有非常好的帮助,需要的朋友可以参考下
    2021-04-04
  • java如何获取用户登录ip、浏览器信息、SessionId

    java如何获取用户登录ip、浏览器信息、SessionId

    这篇文章主要介绍了java如何获取用户登录ip、浏览器信息、SessionId,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11
  • Java用jxl读取excel并保存到数据库的方法

    Java用jxl读取excel并保存到数据库的方法

    这篇文章主要为大家详细介绍了Java用jxl读取excel并保存到数据库的方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-10-10
  • Struts2学习笔记(3)-DMI动态调用方式

    Struts2学习笔记(3)-DMI动态调用方式

    本文主要介绍Struts2的DMI动态调用的两种方式,简单实用,希望能给大家做一个参考。
    2016-06-06
  • SpringBoot中AOP的动态匹配和静态匹配详解

    SpringBoot中AOP的动态匹配和静态匹配详解

    这篇文章主要介绍了SpringBoot中AOP的动态匹配和静态匹配详解,在创建代理的时候对目标类的每个连接点使用静态切点检查,如果仅通过静态切点检查就可以知道连接点是不匹配的,则在运行时就不再进行动态检查了,需要的朋友可以参考下
    2023-09-09
  • SpringBoot普通类获取spring容器中bean的操作

    SpringBoot普通类获取spring容器中bean的操作

    这篇文章主要介绍了SpringBoot普通类获取spring容器中bean的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • SpringBoot3.x打包Docker容器的实现

    SpringBoot3.x打包Docker容器的实现

    这篇文章主要介绍了SpringBoot3.x打包Docker容器的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-11-11

最新评论