CAS

   CAS 定义:操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置V的值与预期原值A相匹配,那么处理器会自动将该位置值更新为新值B 。否则,处理器不做任何操作。

 CAS是乐观锁的一种实现,适用于读操作较多的场景。并且没有加锁,是无锁操作,所以不会出现死锁。

 CAS原理: 通过调用Unsafe类里Java Native(JNI:Java Native Interface)本地方法实现的。是Java借助C调用CPU底层指令实现的。

   CAS缺点:

 1.ABA 问题,如果一个值原来是A,变成了B,又变成了A,那么使用CAS检查时会发现它的值没有发生变化,但是实际上却变化了。

  解决思路:使用版本号,每次变量前追加版本号(比如 AtomicStampedReference类,通过一个键值对Pair存储数据和时间戳,在更新时对数据和时间戳进行比较,两者都符合预期才进行新值替换)

 2.循环时间长开销大。自旋长时间不成功,会带来CPU消耗

  解决思路:自旋一定次数后放弃。

 3.只能保证一个共享变量的原子操作。

  解决思路:可以通过AtomicReference类把多个变量放在一个对象里来进行CAS操作

 CAS的应用:

  一般是CAS和Volatile修饰的变量一起使用。有一个通用的实现模式。

   1、首先,声明共享变量为volatile;

   2、然后,使用CAS的原子条件更新来实现线程之间的同步;

   3、同时,配合以volatile的读/写和CAS所具有的volatile读和写的内存语义来实现线程之间的通信。

    

   AQS,原子变量类(java.util.concurrent.atomic包中的类),非阻塞数据结构 就是这样实现的

  

JVM中的CAS应用(堆中对象的分配):

 Java调用new Object()会创建一个对象,这个对象会被分配到JVM的堆中。那么这个对象到底是怎么在堆中保存的呢:

    在单线程的情况下,一般有两种分配策略:

      1. 指针碰撞:这种一般适用于内存是绝对规整的(内存是否规整取决于内存回收策略),分配空间的工作只是将指针像空闲内存一侧移动对象大小的距离即可。

      2. 空闲列表:这种适用于内存非规整的情况,这种情况下JVM会维护一个内存列表,记录哪些内存区域是空闲的,大小是多少。给对象分配空间的时候去空闲列表里查询到合适的区域然后进行分配即可。

    但是JVM不可能一直在单线程状态下运行,那样效率太差了。由于再给一个对象分配内存的时候不是原子性的操作,至少需要以下几步:查找空闲列表、分配内存、修改空闲列表等等,这是不安全的。解决并发时的安全问题也有两种策略:

      1. CAS:实际上虚拟机采用CAS配合上失败重试的方式保证更新操作的原子性,原理和上面讲的一样。

      2. TLAB:如果使用CAS其实对性能还是会有影响的,所以JVM又提出了一种更高级的优化策略:每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲区(TLAB),线程内部需要分配内存时直接在TLAB上分配就行,避免了线程冲突。只有当缓冲区的内存用光需要重新分配内存的时候才会进行CAS操作分配更大的内存空间。 
      虚拟机是否使用TLAB,可以通过-XX:+/-UseTLAB参数来进行配置(jdk5及以后的版本默认是启用TLAB的)。

原文地址:https://www.cnblogs.com/liumz0323/p/13996880.html