博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JAVA中的CAS
阅读量:4481 次
发布时间:2019-06-08

本文共 5981 字,大约阅读时间需要 19 分钟。

一、CAS概念与原理

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

  实现思想 CAS(V, A, B),V为内存地址、A为预期原值,B为新值。如果内存地址的值与预期原值相匹配,那么将该位置值更新为新值。否则,说明已经被其他线程更新,处理器不做任何操作;无论哪种情况,它都会在 CAS 指令之前返回该位置的值。而我们可以使用自旋锁,循环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 value100 */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 add109 * @return the updated value110 */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类来处理这种情况,可以将多个变量放到一个对象中。

  

 

转载于:https://www.cnblogs.com/Shuuichi/p/10590710.html

你可能感兴趣的文章
[leetcode] Serialize and Deserialize Binary Tree
查看>>
MySQL必知必会(Insert into)
查看>>
Redhat配置多Mysql实例
查看>>
递归要记得返回
查看>>
UIScrollView
查看>>
s6k0:一种输入法分词关联模型演示
查看>>
Oracle总结
查看>>
ionic3带参数返回原来页面
查看>>
Static关键字
查看>>
项目测试Wikis(1)门户_用户接口
查看>>
MUI 样式按钮的禁用
查看>>
选择排序线性顺序版
查看>>
为什么要把系统拆分成分布式的,为啥要用Dubbo?
查看>>
servlet乱码问题
查看>>
从C到C++ (2)
查看>>
欢迎来到我的博客!
查看>>
LeedCde 题解目录
查看>>
二叉树的二叉线索存储
查看>>
linux入门实验手册5 用户和权限管理
查看>>
Extjs实现Grid表格显示【一】
查看>>