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

JAVA中的CAS

程序员文章站 2023-12-10 08:52:04
一、CAS概念与原理 CAS,全称Compare And Swap(比较与交换),解决多线程并行情况下使用锁造成性能损耗的一种机制。 实现思想 CAS(V, A, B),V为内存地址、A为预期原值,B为新值。如果内存地址的值与预期原值相匹配,那么将该位置值更新为新值。否则,说明已经被其他线程更新,处 ......

一、cas概念与原理

  cas,全称compare and swap(比较与交换),解决多线程并行情况下使用锁造成性能损耗的一种机制。

  实现思想 cas(v, a, b),v为内存地址、a为预期原值,b为新值。如果内存地址的值与预期原值相匹配,那么将该位置值更新为新值。否则,说明已经被其他线程更新,处理器不做任何操作;无论哪种情况,它都会在 cas 指令之前返回该位置的值。而我们可以使用自旋锁,循环cas,重新读取该变量再尝试再次修改该变量,也可以放弃操作。

JAVA中的CAS

  cas操作由处理器提供支持,是一种原语。原语是操作系统或计算机网络用语范畴。是由若干条指令组成的,用于完成一定功能的一个过程,具有不可分割性,即原语的执行必须是连续的,在执行过程中不允许被中断。如 intel 处理器,比较并交换通过指令的 cmpxchg 系列实现。处理器相关指令不做过多介绍,有兴趣的可自行查阅资料。

 

二、jdk1.8 中的cas

  unsafe类,在sun.misc包下,不属于java标准。unsafe类提供一系列增加java语言能力的操作,如内存管理、操作类/对象/变量、多线程同步等。其中与cas相关的方法有以下几个:

//var1为cas操作的对象,offset为var1某个属性的地址偏移值,expected为期望值,var2为要设置的值,利用jni来完成cpu指令的操作
public final native boolean compareandswapobject(object var1, long offset, object expected, object var2);
public final native boolean compareandswapint(object var1, long offset, int expected, int var2);
public final native boolean compareandswaplong(object var1, long offset, long expected, long var2);

  

    /** 如果cas成功,return oldvalue, oldvalue =  oldvalue + addvalue
     *  如果cas失败,自旋,一直运行,直到成功为止
     */
    public final xxx getandaddxxx(object var1, long offset, long addvalue) {
        int oldvalue;
        do {
            oldvalue = this.getintvolatile(var1, offset);
        } while(!this.compareandswapint(var1, offset, oldvalue, oldvalue + addvalue));

        return oldvalue;
    }

    /** 如果cas成功,return oldvalue, oldvalue =  newvalue
     *  如果cas失败,自旋,一直运行,直到成功为止
     */
    public final xxx getandsetxxx(object var1, long offset, object newvalue) {
        int oldvalue;
        do {
            oldvalue = this.getxxxvolatile(var1, offset);
        } while(!this.compareandswapxxx(var1, offset, oldvalue, newvalue));

        return oldvalue;
    }

  一般不建议使用unsafe类,除非对它有很深入的了解。

  

  java.util.concurrent包中大量使用了cas原理,如atomicinteger类,都是调用上面几个unsafe方法保证多线程数据的正确性

  以下是atomicinteger的cas操作相关源码

  1 public class atomicinteger extends number implements java.io.serializable {
  2     private static final long serialversionuid = 6214790243416807050l;
  3 
  4     // setup to use unsafe.compareandswapint for updates
  5     // unsafe类,提供一系列增强java的功能,如内存管理、操作类/对象/变量、多线程同步等。不建议开发者调用
  6     private static final unsafe unsafe = unsafe.getunsafe();
  7     // 获取对象某个属性的地址偏移值
  8     private static final long valueoffset;
  9 
 10     static {
 11         try {
 12             // value相对“起始地址”的偏移量
 13             valueoffset = unsafe.objectfieldoffset
 14                     (atomicinteger.class.getdeclaredfield("value"));
 15         } catch (exception ex) { throw new error(ex); }
 16     }
 17 
 18     // value值, volatile修饰,保证不同线程间的可见性
 19     private volatile int value;
 20     public atomicinteger(int initialvalue) { value = initialvalue; }
 21     public atomicinteger() {}
 22 
 23     public final int get() { return value; }
 24     public final void set(int newvalue) { value = newvalue; }
 25 
 26     /**
 27      * eventually sets to the given value.
 28      *
 29      * @param newvalue the new value
 30      * @since 1.6
 31      */
 32     public final void lazyset(int newvalue) {
 33         //有序或者有延迟的putintvolatile方法
 34         unsafe.putorderedint(this, valueoffset, newvalue);
 35     }
 36 
 37     /**
 38      * atomically sets to the given value and returns the old value.
 39      * @param newvalue the new value
 40      * @return the previous value
 41      */
 42     public final int getandset(int newvalue) {
 43         return unsafe.getandsetint(this, valueoffset, newvalue);
 44     }
 45 
 46     /**
 47      * atomically sets the value to the given updated value
 48      * if the current value {@code ==} the expected value.
 49      * @param expect the expected value
 50      * @param update the new value
 51      * @return {@code true} if successful. false return indicates that
 52      * the actual value was not equal to the expected value.
 53      */
 54     public final boolean compareandset(int expect, int update) {
 55         // jni调用,实现cas
 56         return unsafe.compareandswapint(this, valueoffset, expect, update);
 57     }
 58 
 59     /**
 60      * i++ 操作
 61      * atomically increments by one the current value.
 62      * @return the previous value
 63      */
 64     public final int getandincrement() {
 65         return unsafe.getandaddint(this, valueoffset, 1);
 66     }
 67 
 68     /**
 69      * i-- 操作
 70      * atomically decrements by one the current value.
 71      * @return the previous value
 72      */
 73     public final int getanddecrement() {
 74         return unsafe.getandaddint(this, valueoffset, -1);
 75     }
 76 
 77     /**
 78      * return i, i = i + n 操作
 79      * atomically adds the given value to the current value.
 80      * @param delta the value to add
 81      * @return the previous value
 82      */
 83     public final int getandadd(int delta) {
 84         return unsafe.getandaddint(this, valueoffset, delta);
 85     }
 86 
 87     /**
 88      * ++i 操作
 89      * atomically increments by one the current value.
 90      * @return the updated value
 91      */
 92     public final int incrementandget() {
 93         return unsafe.getandaddint(this, valueoffset, 1) + 1;
 94     }
 95 
 96     /**
 97      * --i 操作
 98      * atomically decrements by one the current value.
 99      * @return the updated value
100      */
101     public final int decrementandget() {
102         return unsafe.getandaddint(this, valueoffset, -1) - 1;
103     }
104 
105     /**
106      * i = i + n ,return i操作
107      * atomically adds the given value to the current value.
108      * @param delta the value to add
109      * @return the updated value
110      */
111     public final int addandget(int delta) {
112         return unsafe.getandaddint(this, valueoffset, delta) + delta;
113     }
114     // 其余函数,略...
115

  

三、cas缺点

  cas有几个缺点:

  1、aba问题。当第一个线程执行cas操作,尚未修改为新值之前,内存中的值已经被其他线程连续修改了两次,使得变量值经历 a -> b -> a的过程。

  解决方案:添加版本号作为标识,每次修改变量值时,对应增加版本号; 做cas操作前需要校验版本号。jdk1.5之后,新增atomicstampedreference类来处理这种情况。

  2、循环时间长开销大。如果有很多个线程并发,cas自旋可能会长时间不成功,会增大cpu的执行开销。

  3、只能对一个变量进原子操作。jdk1.5之后,新增atomicreference类来处理这种情况,可以将多个变量放到一个对象中。