原子操作CAS

原子操作

    假设有A、B两个操作,从执行A操作的线程来看,执行B操作的线程要么完全执行B操作,要么完全不执行B操作,A、B对彼此来说就是原子操作

实现

1、使用锁机制,synchronized 关键字就是基于阻塞的锁机制,即当一个线程拥有锁的时候,访问同一资源的其它线程需要等待,直到该线程释放锁。这是一种粒度较粗的机制,也会带来一些其他问题,如多线程并发时,消耗大量的CPU资源;死锁;线程持有锁一直不释放等。

2、使用现代处理器基本都支持的CAS指令。

CAS(Compare And Swap)实现思路

CAS是无锁化实现原子操作,每一个 CAS 操作过程都包含三个运算符:一个内存地址 V,一个期望的值 A 和一个新值 B,操作的时候如果这个地址上存放的值等于这个期望的值 A,则将地址上的值赋为新值 B,否则不做任何操作。循环CAS 就是通过自旋(死循环)不断的做 cas 操作,直到成功为止。它是利用CPU的多处理能力,实现硬件层面上的阻塞,再加上volatile特性来实现基于原子操作的线程安全。


CAS的缺陷

1、ABA问题:

举个通俗点的例子,你倒了一杯水放桌子上,干了点别的事,然后同事把你水喝了又给你重新倒了一杯水,你回来看水还在,拿起来就喝,如果你不管水中间被人喝过,只关心水还在,这就是 ABA 问题。

因为CAS 需要在操作值的时候,检查操作值有没有发生变化,如果没有发生变化则更新。但是如果一个值原来是 A,变成了 B,又变成了 A,那么使用 CAS 进行检查时会发现它的值没有发生变化,但是实际上却变化了。

解决思路:使用版本号,每次操作值变化,给版本号加1,这样再进行CAS时,检查操作数值和版本号是否都不变,再给交换新值。

2、自旋时间长,一直不成功,会使CPU资源消耗变大。

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

当对一个共享变量执行操作时,我们可以使用循环 CAS 的方式来保证原子操作,但是对多个共享变量操作时,循环 CAS 就无法保证操作的原子性,这个时候就可以用锁。

还有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如,有两个共享变量 i=2,j=a,合并一下 ij=2a,然后用 CAS 来操作 ij。从 Java 1.5开始,JDK 提供了 AtomicReference 类来保证引用对象之间的原子性,就可以把多个变量放在一个对象里来进行 CAS 操作。


Jdk 中相关原子操作类的使用

AtomicInteger

•int addAndGet(int delta):以原子方式将输入的数值与实例中的值(AtomicInteger 里的 value)相加,并返回结果。

•boolean compareAndSet(int expect,int update):如果输入的数值等于预期值,则以原子方式将该值设置为输入的值。

•int getAndIncrement():以原子方式将当前值加 1,注意,这里返回的是自增前的值。

•int getAndSet(int newValue):以原子方式设置为 newValue 的值,并返回旧值。

AtomicIntegerArray

主要是提供原子的方式更新数组里的整型,其常用方法如下。

•int addAndGet(int i,int delta):以原子方式将输入值与数组中索引 i 的元素相加。

•boolean compareAndSet(int i,int expect,int update):如果当前值等于预期值,则以原子方式将数组位置 i 的元素设置成 update 值。

需要注意的是,数组 value 通过构造方法传递进去,然后 AtomicIntegerArray会将当前数组复制一份,所以当 AtomicIntegerArray 对内部的数组元素进行修改时,不会影响传入的数组。 

更新引用类型

原子更新基本类型的 AtomicInteger,只能更新一个变量,如果要原子更新多个变量,就需要使用这个原子更新引用类型提供的类。Atomic 包提供了以下 3个类。

AtomicReference:原子更新引用类型。

AtomicStampedReference:利用版本戳的形式记录了每次改变以后的版本号,这样的话就不会存在 ABA问题。这就是AtomicStampedReference 的解决方案。AtomicMarkableReference跟 AtomicStampedReference 差不多,AtomicStampedReference 是使用 pair 的 int stamp 作为计数器使用,AtomicMarkableReference 的 pair 使用的是 boolean mark。

还是那个水的例子,AtomicStampedReference 可能关心的是动过几次,AtomicMarkableReference 关心的是有没有被人动过,方法都比较简单。

AtomicMarkableReference:原子更新带有标记位的引用类型。可以原子更新一个布尔类型的标记位和引用类型。构造方法是 AtomicMarkableReference(V initialRef,booleaninitialMark)。 

原子更新字段类

如果需原子地更新某个类里的某个字段时,就需要使用原子更新字段类,Atomic 包提供了以下 3 个类进行原子字段更新。

要想原子地更新字段类需要两步。

第一步,因为原子更新字段类都是抽象类,每次使用的时候必须使用静态方法 newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。

第二步,更新类的字段(属性)必须使用 public volatile修饰符。

AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。

AtomicLongFieldUpdater:原子更新长整型字段的更新器。

AtomicReferenceFieldUpdater:原子更新引用类型里的字段。

你可能感兴趣的:(原子操作CAS)