Atomic 原子类详解

一、介绍

  在 Java 1.5后引入了原子类操作类,这些操作的加入,让我们不用锁即可以实现原子操作,更加高效,简洁。

  Atomic包下所有的原子类都只适用于单个元素,即只能保证一个基本数据类型、对象、或者数组的原子性。根据使用范围,可以将这些类分为四种类型,分别为原子更新基本类型原子更新数组原子更新引用原子更新属性

 

1、原子更新基本类型

  原子更新基本类型包括 AtomicInteger、AtomicLong、AtomicBoolean 三种,分别对应3种基本数据类型,并且提供了几项基本操作:

 1 // 获取当前值,然后自加,相当于i++
 2 getAndIncrement()
 3 // 获取当前值,然后自减,相当于i--
 4 getAndDecrement()
 5 // 自加1后并返回,相当于++i
 6 incrementAndGet()
 7 // 自减1后并返回,相当于--i
 8 decrementAndGet()
 9 // 获取当前值,并加上预期值
10 getAndAdd(int delta)
11 // 获取当前值,并设置新值
12 int getAndSet(int newValue)

2、原子更新引用类型

  基本数据类型只能更新单个变量,如果要更新多个变量就需要用原子更新引用类型,引用类型的原子类包括AtomicReference、AtomicStampedReference、AtomicMarkableReference三个。

  AtomicReference:引用原子类。

  AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。(关于CAS及ABA问题后文详细分析)

  AtomicMarkableReference:原子类更新带有标记的引用类型,该类将 boolean值与引用关联起来。

3、原子类更新数组

  这里原子更新数组并不是对数组本身的原子操作,而是对数组中的元素。主要包括3个类:AtomicIntegerArray、AtomicLongArray及AtomicReferenceArray,分别表示原子更新整数数组的元素、原子更新长整数数组的元素以及原子更新引用类型数组的元素
 

4、原子更新对象属性

如果直选哟更新某个对象中的某个字段,可以使用更新对象字段的原子类。包括三个类,AtomicIntegerFieldUpdater、AtomicLongFieldUpdater以及AtomicReferenceFieldUpdater。需要注意的是这些类的使用需要满足以下条件才可。

  • 被操作的字段不能是static类型;
  • 被操纵的字段不能是final类型;
  • 被操作的字段必须是volatile修饰的;
  • 属性必须对于当前的Updater所在区域是可见的。

二、CAS

  CAS操作是原子类实现的核心操作,CAS是Compare And Swap的简称,即比较并交换的意思。CAS是一种无锁算法,其算法思想如下:
 
  CAS的函数公式:compareAndSwap(V,E,N); 其中V表示要更新的变量,E表示预期值,N表示期望更新的值。调用compareAndSwap函数来更新变量V,如果V的值等于期望值E,那么将其更新为N,如果V的值不等于期望值E,则说明有其它线程跟新了这个变量,此时不会执行更新操作,而是重新读取该变量的值再次尝试调用compareAndSwap来更新。
  
  可见CAS其实存在一个循环的过程,如果有多个线程在同时修改这一个变量V,在修改之前会先拿到这个变量的值,再和变量对比看是否相等,如果相等,则说明没有其它线程修改这个变量,自己更新变量即可。如果发现要修改的变量和期望值不一样,则说明再读取变量V的值后,有其它线程对变量V做了修改,那么,放弃本次更新,重新读变量V的值,并再次尝试修改,直到修改成功为止。这个循环过程一般也称作自旋,CAS操作的整个过程如下图所示:

 

 

CAS的缺点:
 
1、只能保证单个变量共享变量都原子性,如果要保证多个变量或者代码块块的原子性就必须加锁
 
2、存在内存开销问题,自旋会占用一定的cup资源
 
3、ABA问题:

因为CAS是通过检查值有没有发生改变来保证原子性的,假若一个变量V的值为A,线程1和线程2同时都读取到了这个变量的值A,此时线程1将V的值改为了B,然后又改回了A,期间线程2一直没有抢到CPU时间片。知道线程1将V的值改回A后线程2才得到执行。那么此时,线程2并不知道V的值曾经改变过。这个问题就被成为ABA问题。

ABA问题的解决其实也容易处理,即添加一个版本号,更次更新值同时也更新版本号即可。上文中提到的AtomicStampedReference就是用来解决ABA问题的。

 

CPU对 CAS 的支持:

  在操作系统中CAS是一种系统原语,原语由多条指令组成,且原语的执行是连续不可中断的。因此CAS实际上是一条CPU的原子指令,虽然看上去CAS是一个先比较再交换的操作,但实际上这个过程是由CPU保证了原子操作。

 

三、原子类的实现 - UnSafe类 - 直接操作内存

  Unsafe是一个神奇且鲜为人知的Java类,因为在平时开发中很少用到它。但是这个类中为我们提供了相当多的功能,它即可以让Java语言像C语言指针一样操作内存,同时还提供了CAS、内存屏障、线程调度、对象操作、数组操作等能力,如下图:

 

 

  

 

posted @ 2022-03-09 16:39  空心小木头  阅读(382)  评论(0编辑  收藏  举报