• CAS


    一、引言                                                                                                                       

    Java5以来,新增加的java.util.concurrent.atomic并发包中的一些原子类,是对Java部分数据类型的原子封装,在原有数据类型的基础上,提供了原子性的操作方法,保证了线程安全。其就是建立在CAS之上的。

    下面以AtomicInteger为例,来看一下是如何实现原子性操作的?

     1 public final int incrementAndGet() {
     2 
     3 for (;;) {
     4 
     5 int current = get();
     6 
     7 int next = current + 1;
     8 
     9 if (compareAndSet(current, next))
    10 
    11 return next;
    12 
    13 }
    14 
    15 }
    16 
    17 public final int decrementAndGet() {
    18 
    19 for (;;) {
    20 
    21 int current = get();
    22 
    23 int next = current - 1;
    24 
    25 if (compareAndSet(current, next))
    26 
    27 return next;
    28 
    29 }
    30 
    31 }

       

    以这两个方法为例,incrementAndGet方法相当于原子性的++i,decrementAndGet方法相当于原子性的--i(我们知道++i或--i不是一个原子性的操作),这两个方法中都没有使用阻塞式的方式来保证原子性(如Synchronized),那它们是如何保证原子性的呢,下面引出CAS。

       

    二、Compare And Swap                                                                                                

           CAS 指的是现代 CPU 广泛支持的一种对内存中的共享数据进行操作的一种特殊指令。这个指令会对内存中的共享数据做原子的读写操作。简单介绍一下这个指令的操作过程:首先,CPU 会将内存中将要被更改的数据与期望的值做比较。然后,当这两个值相等时,CPU 才会将内存中的数值替换为新的值。否则便不做操作。最后,CPU 会将旧的数值返回。这一系列的操作是原子的。它们虽然看似复杂,但却是 Java 5 并发机制优于原有锁机制的根本。简单来说,CAS 的含义是"我认为原有的值应该是什么,如果是,则将原有的值更新为新值,否则不做修改,并告诉我原来的值是多少"。(这段描述引自《Java并发编程实践》)

          我们可以认为,CAS的操作包括3个操作数:内存位置V,预期原值A,新值B。

    当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则返回V。因此可以理解CAS的主要就是两个步骤:冲突检测和数据更新。

    这是一种乐观锁的思路,它相信在它修改之前,没有其它线程去修改它;而Synchronized是一种悲观锁,它认为在它修改之前,一定会有其它线程去修改它,悲观锁效率很低。

    volatile变量

    与锁相比,volatile变量是一个更轻量级的同步机制,因为在使用这些变量时不会发生上下文切换和线程调度等操作,但是volatile不能解决原子性问题,因此同步还是需要锁机制来解决。

    //首先声明了一个volatile变量value,我们知道volatile保证了变量的内存可见性,也就是所有工作线程中同一时刻都可以得到一致的值。
    
    private volatile int value;
    
    public final int get() {
    
    return value;
    
    }

    下面来看一下AtomicInteger是如何利用CAS实现原子性操作的。   

    三、Compare And Set                                                                                                     

     1 // setup to use Unsafe.compareAndSwapInt for updates
     2 
     3 private static final Unsafe unsafe = Unsafe.getUnsafe();
     4 
     5 private static final long valueOffset;// 注意是静态的
     6 
     7    
     8 
     9 static {
    10 
    11 try {
    12 
    13 valueOffset = unsafe.objectFieldOffset
    14 
    15 (AtomicInteger.class.getDeclaredField("value"));// 反射出value属性,获取其在内存中的位置
    16 
    17 } catch (Exception ex) { throw new Error(ex); }
    18 
    19 }
    20 
    21    
    22 
    23 public final boolean compareAndSet(int expect, int update) {
    24 
    25 return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    26 
    27 }

    比较并设置,这里利用Unsafe类的JNI方法实现,使用CAS指令,可以保证读-改-写是一个原子操作。compareAndSwapInt有4个参数,this - 当前AtomicInteger对象,Offset - value属性在内存中的位置(需要强调的是不是value值在内存中的位置),expect - 预期值,update - 新值,根据上面的CAS操作过程,当内存中的value值等于expect值时,则将内存中的value值更新为update值,并返回true,否则返回false。在这里我们有必要对Unsafe有一个简单点的认识,从名字上来看,不安全,确实,这个类是用于执行低级别的、不安全操作的方法集合,这个类中的方法大部分是对内存的直接操作,所以不安全,但当我们使用反射、并发包时,都间接的用到了Unsafe。

       

    循环设置

    现在在来看开篇提到的两个方法,我们拿incrementAndGet来分析一下其实现过程。

     1 public final int incrementAndGet() {
     2 
     3 for (;;) {// 这样优于while(true)
     4 
     5 int current = get();// 获取当前值
     6 
     7 int next = current + 1;// 设置更新值
     8 
     9 if (compareAndSet(current, next))
    10 
    11 return next;
    12 
    13 }
    14 
    15 }

    循环内,获取当前值并设置更新值,调用compareAndSet进行CAS操作,如果成功就返回更新至,否则重试到成功为止。这里可能存在一个隐患,那就是循环时间过长,总是在当前线程compareAndSet时,有另一个线程设置了value,这个当然是属于小概率时间,目前Java貌似还不能处理这种情况。

       

    缺点

    虽然使用CAS可以实现非阻塞式的原子性操作,但是会产生ABA问题,下篇文章阐述关于ABA问题。

       

    四、小结                                                                                                                              

    现代的CPU提供了特殊的指令,可以自动更新共享数据,而且能够检测到其他线程的干扰,而 compareAndSet() 就用这些代替了锁定。

    在没有锁的机制下需要借助volatile原语,保证线程间的数据是可见的(共享的)。这样才获取变量的值的时候才能直接读取。

    乐观锁可以认为是一种思想,CAS(Compare and Swap)是乐观锁的一种实现。

    Compare And Set整体的过程就是这样子的,利用CPU的CAS指令,同时借助JNI来完成Java的非阻塞算法。其它原子操作都是利用类似的特性完成的。

       

    转载 http://blog.csdn.net/ghsau/article/details/38471987

  • 相关阅读:
    你所不了解的静态路由特点及配置
    程序员进阶中--说说这一年的“酸甜苦辣”
    前序、中序、后序遍历的多种非递归实现
    spring依赖注入单元测试:expected single matching bean but found 2
    汉语-汉字:効、效
    汉语-词语:悃愊
    汉语-词语:宽容
    System.Threading.Tasks.TaskFactory.cs
    汉语-词语:高明
    唐-诗:《山居秋暝》
  • 原文地址:https://www.cnblogs.com/exceptioneye/p/5373489.html
Copyright © 2020-2023  润新知