资讯专栏INFORMATION COLUMN

CAS

meislzhua / 3086人阅读

摘要:如果程序是在多处理器上运行,就为指令加上前缀。关于的锁有如下种处理器自动保证基本内存操作的原子性首先处理器会自动保证基本的内存操作的原子性。使用缓存锁保证原子性第二个机制是通过缓存锁定保证原子性。

前言 概述

与锁不同的是, CAS 是一种无锁操作,一种无阻塞的算法,它实质上不能说是一种锁,而是将 CPU 充分利用起来的一种算法

CAS 广泛应用在数据结构中,JDK中的 java.util.concurrent 并发包就是在其操作下建立的

众所周知,JAVA 作为一门高级语言,是不支持一些底层处理的,例如指针,内存控制等等,但大家可以看看 sun.misc.Unsafe 类,也是在它的支持下,JAVA 具备了对硬件级别原子操作的支持,这个包有很多应用,例如 java.util.concurrent.atomic 包下的原子类都是基于其实现 CAS 操作的

我的测试下,当线程数量不大时,CAS 要快于锁,但线程数量很多很多时,CAS 却更慢了

参考

http://blog.csdn.net/hsuxu/ar...
http://www.cnblogs.com/mickol...

CAS 概述

举个例子,如 i++,它是分三步的

先取内存中的 i

再将 i 加上 1

最后将加完后的值赋给内存中的 i

但若在其赋值前,i 的内存值已经被其他线程修改,此处肯定会丢失数据,也就是说它是线程不安全的

如果给这个操作加上锁,那代价未免也太大了,CAS 便可以更快地解决这个问题

原理

CAS 的原理其实很简单,主要分三个参数

内存值 - 内存里的实际值

旧期望值 - 操作前的值

新值 - 操作后的值

CAS 的操作简而言之就是 compare and swap

将内存值与旧期望值比较

若相等,则说明此值在操作中没有被其他线程改变过,并将新值赋给内存值

若不等,则说明此值在操作中已经被其他线程改变过,并一直自旋直到相等

ABA 问题

简而言之,ABA 问题就是,比如我取内存值 A,在我比较之前,它被其他人改成了 B,然后又被其他人改回了 A,而我之后再做比较,相等成立,但是又会造成数据丢失的问题

CAS 真正比较的应该是 值的状态,而不是值的大小,我可以给值附带一个 版本号,然后更新时对版本号进行值大小的 CAS 操作,或者附带一个 时间戳也是一样的,像现在数据库大部分都是采用附加版本号的方法

大家也可以看看 java.util.concurrent.atomic.AtomicStampedReference 是怎么解决 ABA 问题的,在这里就不讲述了

缺点

如果每个人都在自旋,CPU 的开销将是巨大的,关于本人的测试,当线程数量很多很多时,CAS 会更慢就是这个原因

AtomicInteger

我们来看看 java.util.concurrent.atomic.AtomicInteger 是怎么实现原子操作的,其主要成员如下

// Unsafe 类实例,具体的在下一篇文章详细讲,这里先跳过
private static final Unsafe unsafe = Unsafe.getUnsafe();
// 值偏移量
private static final long valueOffset;
// 内部封装值
private volatile int value;
// unsafe 初始化
static {
    try {
        valueOffset = unsafe.objectFieldOffset
            (AtomicInteger.class.getDeclaredField("value"));
    } catch (Exception ex) { 
        throw new Error(ex); 
    }
}

我们常用的 incrementAndGet 方法如下,在这里是直接调用 Unsafe 的 native 方法,实现硬件级别的原子操作,底层是用汇编实现的

public final int incrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

这个本地方法在openjdk中依次调用的c++代码为:unsafe.cpp,atomic.cpp和atomicwindowsx86.inline.hpp。这个本地方法的最终实现在openjdk的如下位置:openjdk-7-fcs-src-b147-27jun2011openjdkhotspotsrcoscpuwindowsx86vm atomicwindowsx86.inline.hpp(对应于windows操作系统,X86处理器)。下面是对应于intel x86处理器的源代码的片段:

// Adding a lock prefix to an instruction on MP machine
// VC++ doesn"t like the lock prefix to be on a single line
// so we can"t insert a label after the lock prefix.
// By emitting a lock prefix, we can define a label after it.
#define LOCK_IF_MP(mp) __asm cmp mp, 0  
                       __asm je L0      
                       __asm _emit 0xF0 
                       __asm L0:

inline jint Atomic::cmpxchg (jint exchange_value, 
                             volatile jint* dest, 
                             jint compare_value) {
  // alternative for InterlockedCompareExchange
  int mp = os::is_MP();
  __asm {
    mov edx, dest
    mov ecx, exchange_value
    mov eax, compare_value
    LOCK_IF_MP(mp)
    cmpxchg dword ptr [edx], ecx
  }
}

如上面源代码所示,程序会根据当前处理器的类型来决定是否为cmpxchg指令添加lock前缀。如果程序是在多处理器上运行,就为cmpxchg指令加上lock前缀(lock cmpxchg)。反之,如果程序是在单处理器上运行,就省略lock前缀(单处理器自身会维护单处理器内的顺序一致性,不需要lock前缀提供的内存屏障效果)。

CPU

关于CPU的锁有如下3种:

处理器自动保证基本内存操作的原子性

首先处理器会自动保证基本的内存操作的原子性。处理器保证从系统内存当中读取或者写入一个字节是原子的,意思是当一个处理器读取一个字节时,其他处理器不能访问这个字节的内存地址。奔腾6和最新的处理器能自动保证单处理器对同一个缓存行里进行16/32/64位的操作是原子的,但是复杂的内存操作处理器不能自动保证其原子性,比如跨总线宽度,跨多个缓存行,跨页表的访问。但是处理器提供总线锁定和缓存锁定两个机制来保证复杂内存操作的原子性

使用总线锁保证原子性

第一个机制是通过总线锁保证原子性。如果多个处理器同时对共享变量进行读改写(i++就是经典的读改写操作)操作,那么共享变量就会被多个处理器同时进行操作,这样读改写操作就不是原子的,操作完之后共享变量的值会和期望的不一致,举个例子:如果i=1,我们进行两次i++操作,我们期望的结果是3,但是有可能结果是2,如图

原因是有可能多个处理器同时从各自的缓存中读取变量i,分别进行加一操作,然后分别写入系统内存当中。那么想要保证读改写共享变量的操作是原子的,就必须保证CPU1读改写共享变量的时候,CPU2不能操作缓存了该共享变量内存地址的缓存。

处理器使用总线锁就是来解决这个问题的。所谓总线锁就是使用处理器提供的一个LOCK#信号,当一个处理器在总线上输出此信号时,其他处理器的请求将被阻塞住,那么该处理器可以独占使用共享内存。

使用缓存锁保证原子性

第二个机制是通过缓存锁定保证原子性。在同一时刻我们只需保证对某个内存地址的操作是原子性即可,但总线锁定把CPU和内存之间通信锁住了,这使得锁定期间,其他处理器不能操作其他内存地址的数据,所以总线锁定的开销比较大,最近的处理器在某些场合下使用缓存锁定代替总线锁定来进行优化。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/67245.html

相关文章

  • CAS 5.2.x 单点登录 - 搭建服务端和客户端

    摘要:一简介单点登录,简称为,是目前比较流行的企业业务整合的解决方案之一。客户端拦截未认证的用户请求,并重定向至服务端,由服务端对用户身份进行统一认证。三搭建客户端在官方文档中提供了客户端样例,即。 一、简介 单点登录(Single Sign On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系...

    Lin_YT 评论0 收藏0
  • 统一认证 - Apereo CAS 客户端的集成以及小结

    摘要:所以客户端的集成主要是单点登录的集成,客户端指定需要做安全认证的页面,然后的安全包检测校验用户登录情况,并自动与登录页面进行跳转交互。提供了很多配置的方式,有,,以及其他可查官网。但高度自由的一如既往的,没有提供可视化操作的界面。 前两篇介绍了Apereo CAS以及服务器端的安装,但还不够完整,服务端还没有Application真正用起来呢!这篇文章将介绍怎么用起来 集成的目的 客户...

    AbnerMing 评论0 收藏0
  • spring系列---CAS客户端与SpringSecurity集成

    摘要:客户端与集成指定端口请求路径用于单点退出,该过滤器用于实现单点登出功能,可选配置该过滤器用于实现单点登出功能,可选配置。该过滤器使得开发者可以通过来获取用户的登录名。 CAS客户端与SpringSecurity集成 pom.xml org.springframework spring-context 4.3.9....

    hizengzeng 评论0 收藏0
  • 从http验证流程解析CAS单点登录

    JAVA单点登录有好多种方式,譬如用cookie的domain做,用中间代理做等等,但都需要自行做许多开发工作。而其中耶鲁大学的开源项目CAS提供了一个一站式解决方案,只需很少的扩展即可轻松实现企业级单点登录。基础知识网上其他挺多的,这里我就不详述了。本文通过分析http请求过程中httpheader,cookie等数据剖析了cas(非代理模式,默认验证逻辑。其他如restletAPI等可扩展逻辑...

    honhon 评论0 收藏0
  • 前后端分离下的CAS跨域流程分析

    摘要:这种情况通常发生在反向代理的时候,前端发起请求代理服务器,代理服务器发起请求到,这时候就容易导致域名不一致,请一定要注意这点。 写在最前 前后端分离其实有两类: 开发阶段使用dev-server,生产阶段是打包成静态文件整个放入后端项目中。 开发阶段使用dev-server,生产阶段是打包成静态文件放入单独的静态资源服务器中,如nginx。 这两种方案最大的区别就是生产阶段。由于第...

    ckllj 评论0 收藏0
  • 前后端分离下的CAS跨域流程分析

    摘要:这种情况通常发生在反向代理的时候,前端发起请求代理服务器,代理服务器发起请求到,这时候就容易导致域名不一致,请一定要注意这点。 写在最前 前后端分离其实有两类: 开发阶段使用dev-server,生产阶段是打包成静态文件整个放入后端项目中。 开发阶段使用dev-server,生产阶段是打包成静态文件放入单独的静态资源服务器中,如nginx。 这两种方案最大的区别就是生产阶段。由于第...

    DevTalking 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<