5G发展或催生技术变革 品牌营销再迎新局面
Java中提供了很多原子操作类来保证共享变量操作的原子性。这些原子操作的底层原理都是使用了CAS机制。在使用一门技术之前,了解这个技术的底层原理是非常重要的,所以本篇文章就先来讲讲什么是CAS机制,CAS机制存在的一些问题以及在Java中怎么使用CAS机制。
其实Java并发框架的O ~ A D y O P H基石一共有两块,一块是本文介绍的CAS,另一块就是AQS,后续也会写文章介绍。
什么是CAS机制
CAS机制是一种数据更新i _ T E ( / t的方式。在具体讲什么是CAS机制之前+ l j N F,我们先来聊下在多线程环境下,对共享变量进行数据更新的两种模式:悲观j V 9 r Q锁模式和乐观锁模式。
悲O _ B k J D H l观锁更新的方式认为:在更c { ^ 新数据的时候大概& ? x率会有其他线程去争* D 7 u B Y L夺共享资源,所以悲观锁的做法是:第一个获取资源的线程会将资源锁定起来,其他没争夺到资源的线程只能进入阻塞队列,等第一个获取资源的线程释放锁之后,这些线程才能有机会重新争夺资源。synchronized就是java中悲观锁的| o 2 9典型实现,sx u 9 H B 1 O ; $ynchronized使用起来非常简单方便,但是会使没争抢到资源的线程进入阻塞状态,线程在阻塞状态和Runnable状态之间切换效率较低(比较慢)。比如你的更新操作其实是非常快的,这种情况下你还用synchronized将其他线程都锁住了,线程从Blocked状态切换回Runnable华的时间可能比你的更新操作的时间还要长。
乐观锁更新方式认为:在更新数据的时候其他w ] ^线程争抢这个共享变量的概率非常小,所以更新数据的时候不会对共享数据加锁。但是在正式更新数r p ? U 4 x u ] ?据之前会检查数据是否被H { ` - ? X 3 3 N其他线程改变过,如果未被其他线程改变过就将共享变量更新成最新值,如果发现共享变量已经被其他线2 ( w 4 l N程更新过了,就重试,直到成功为止。CAS机制就是乐观锁的典型实现。
CAS,是Compare and Swap的简称,在这个机制中有三个核心的参数:
- 主内存中存放的共享变量的值:V(y u = r 8 D d一般情况下这个V是内存的地址值,通过这个地址可以获得内存中的值)
- 工作内存中共享变量的副本值,也叫预期值:A
- 需要将共享变量更新到的最新+ U & o ) I值:B
如上图中,主存中保存V值,线程中要使用3 : 5 A p !V值要先从主存中读取V值b I N w e 3 w $ y到线程的工作内存A中,然后M : 4 K _计算后变成B值,最后再把B值写回到内存V值中。多个线程共用V值都是如此操作。CAS的核心是在将B值写入P V b x o到V之前要比较A值和V值是否相同,如果不相同证明此时V值已经被其他线程改变,重新- s v ^ ] .将V值赋给A,并重新计算得到B,如果相同t - 2,则将B值赋给V。
值得注意的是CAS机制中的这步步骤是原子性的(从指令层面提供的原子] O M操作),所以CAS机制` ? ~可以解决多线程并发编程对 q 6 n N Y共享变量读写的原子性问题。
CAS机制优缺点
缺点
1. ABA问题
ABA问题:CAS在操作的时候会检查变量的值是否被更改过,如果) y ?没有则更新值,但是带来一个问题,最开始的值是A,接着变成M W j - [ o , r jB,最后又变成了A。经过检查这个值确实没有修改过,因为最后的值还是A,但是实际上这个值确实已经被修改过了。为了解决这个问题,在每次进行操作的时候加上一个版本号,每次操作的就是两个值,一个版本号和某个值,A——>_ { d Q - ; L ~ RB——>A问题就变成了j x * 1 $ W1A——>2B——>3A。在jdk中提供了AtomicStampedReferenc$ _ / e 7 ` ; e ze类解决ABA问题,用Pair这个内部类s , 1 M D :实现^ @ /,包含两个属性,分别代表版本号和引用,在compareAndSet中先对7 # X F # v当前引用进行检查,再对版l $ g l d本号标志进行检查,只有全部相等才更新值。
2. 可能会消耗较高的CPU
看起来CAS比锁的效率高4 g H 5 & B R 9 I,从阻塞机制变成了非阻塞机制,减少了4 g +线程之间等待的时间。每个方法不能绝对的比另一个好,在线程之间竞争程度大的时候,如果使用CAS,每次都有很多的线程在竞争, M +也就是说CAS机制不能更新成功。这种情况下C, P X ^AS机制会一直重试,这样就会比较耗费CPU。因此可以看出机制p K M W A ^ x %比使用锁机制效率更高;
Java提供的CAS操作类--Unsafe类
从Java5开始引入了对CAS机制的底层的支持,在这之前需要开R M ! : ) P `发人员编写相关的代码才可以实现C: T X { ! m - ZAS。在原子变量类Ato5 Q smic中(例如AtomicInteger、AtomicLong)可以看到CAS操作的代码,在这里的代码都是调用了底层(核心代码调用native修饰的方法)的实现方法。在AtomicInJ 9 K Q 4 Q e b ! Z下面就简单介绍下Unsafe类的API:
- long objectFieldOffset(Field field)方法:返回指定的变量在所属类中的内存偏移地址,该偏移地址仅仅在该Unsafe函数中访问指定字段时R 8 8 5 F F E D 6 2wapLongD G #(var1, v` u U D 3 i v ; Lar2, var6, var4));
return var6;} } s b `
}- long getAndAddLong(Object obj, lo9 ` y ] E } a 2 $ng offset, long addValue)方F r ` /法:获取对象obj中偏移量为offset的变量volatile语义的当前值,并设置变量值为原始值+addValue,原理和上面的方法类似。
CAS使r 3 h | ~ J H用场景
- 使用一个变量统计网站的访问量;
- Atomic类操作;
- 数据库乐观锁更新。
关注我,后续更多干货奉上!