--- title: Atomic 原子类总结 category: Java tag: - Java并发 --- ## Atomic 原子类介绍 `Atomic` 翻译成中文是“原子”的意思。在化学上,原子是构成物质的最小单位,在化学反应中不可分割。在编程中,`Atomic` 指的是一个操作具有原子性,即该操作不可分割、不可中断。即使在多个线程同时执行时,该操作要么全部执行完成,要么不执行,不会被其他线程看到部分完成的状态。 原子类简单来说就是具有原子性操作特征的类。 `java.util.concurrent.atomic` 包中的 `Atomic` 原子类提供了一种线程安全的方式来操作单个变量。 `Atomic` 类依赖于 CAS(Compare-And-Swap,比较并交换)乐观锁来保证其方法的原子性,而不需要使用传统的锁机制(如 `synchronized` 块或 `ReentrantLock`)。 这篇文章我们直接介绍 Atomic 原子类的概念,具体实现原理可以阅读笔者写的这篇文章:[CAS 详解](./cas.md)。 ![JUC原子类概览](https://oss.javaguide.cn/github/javaguide/java/JUC%E5%8E%9F%E5%AD%90%E7%B1%BB%E6%A6%82%E8%A7%88.png) 根据操作的数据类型,可以将 JUC 包中的原子类分为 4 类: **1、基本类型** 使用原子的方式更新基本类型 - `AtomicInteger`:整型原子类 - `AtomicLong`:长整型原子类 - `AtomicBoolean`:布尔型原子类 **2、数组类型** 使用原子的方式更新数组里的某个元素 - `AtomicIntegerArray`:整型数组原子类 - `AtomicLongArray`:长整型数组原子类 - `AtomicReferenceArray`:引用类型数组原子类 **3、引用类型** - `AtomicReference`:引用类型原子类 - `AtomicMarkableReference`:原子更新带有标记的引用类型。该类将 boolean 标记与引用关联起来,~~也可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题~~。 - `AtomicStampedReference`:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。 **🐛 修正(参见:[issue#626](https://github.com/Snailclimb/JavaGuide/issues/626))** : `AtomicMarkableReference` 不能解决 ABA 问题。 **4、对象的属性修改类型** - `AtomicIntegerFieldUpdater`:原子更新整型字段的更新器 - `AtomicLongFieldUpdater`:原子更新长整型字段的更新器 - `AtomicReferenceFieldUpdater`:原子更新引用类型里的字段 ## 基本类型原子类 使用原子的方式更新基本类型 - `AtomicInteger`:整型原子类 - `AtomicLong`:长整型原子类 - `AtomicBoolean`:布尔型原子类 上面三个类提供的方法几乎相同,所以我们这里以 `AtomicInteger` 为例子来介绍。 **`AtomicInteger` 类常用方法** : ```java public final int get() //获取当前的值 public final int getAndSet(int newValue)//获取当前的值,并设置新的值 public final int getAndIncrement()//获取当前的值,并自增 public final int getAndDecrement() //获取当前的值,并自减 public final int getAndAdd(int delta) //获取当前的值,并加上预期的值 boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update) public final void lazySet(int newValue)//最终设置为newValue, lazySet 提供了一种比 set 方法更弱的语义,可能导致其他线程在之后的一小段时间内还是可以读到旧的值,但可能更高效。 ``` **`AtomicInteger` 类使用示例** : ```java // 初始化 AtomicInteger 对象,初始值为 0 AtomicInteger atomicInt = new AtomicInteger(0); // 使用 getAndSet 方法获取当前值,并设置新值为 3 int tempValue = atomicInt.getAndSet(3); System.out.println("tempValue: " + tempValue + "; atomicInt: " + atomicInt); // 使用 getAndIncrement 方法获取当前值,并自增 1 tempValue = atomicInt.getAndIncrement(); System.out.println("tempValue: " + tempValue + "; atomicInt: " + atomicInt); // 使用 getAndAdd 方法获取当前值,并增加指定值 5 tempValue = atomicInt.getAndAdd(5); System.out.println("tempValue: " + tempValue + "; atomicInt: " + atomicInt); // 使用 compareAndSet 方法进行原子性条件更新,期望值为 9,更新值为 10 boolean updateSuccess = atomicInt.compareAndSet(9, 10); System.out.println("Update Success: " + updateSuccess + "; atomicInt: " + atomicInt); // 获取当前值 int currentValue = atomicInt.get(); System.out.println("Current value: " + currentValue); // 使用 lazySet 方法设置新值为 15 atomicInt.lazySet(15); System.out.println("After lazySet, atomicInt: " + atomicInt); ``` 输出: ```java tempValue: 0; atomicInt: 3 tempValue: 3; atomicInt: 4 tempValue: 4; atomicInt: 9 Update Success: true; atomicInt: 10 Current value: 10 After lazySet, atomicInt: 15 ``` ## 数组类型原子类 使用原子的方式更新数组里的某个元素 - `AtomicIntegerArray`:整形数组原子类 - `AtomicLongArray`:长整形数组原子类 - `AtomicReferenceArray`:引用类型数组原子类 上面三个类提供的方法几乎相同,所以我们这里以 `AtomicIntegerArray` 为例子来介绍。 **`AtomicIntegerArray` 类常用方法**: ```java public final int get(int i) //获取 index=i 位置元素的值 public final int getAndSet(int i, int newValue)//返回 index=i 位置的当前的值,并将其设置为新值:newValue public final int getAndIncrement(int i)//获取 index=i 位置元素的值,并让该位置的元素自增 public final int getAndDecrement(int i) //获取 index=i 位置元素的值,并让该位置的元素自减 public final int getAndAdd(int i, int delta) //获取 index=i 位置元素的值,并加上预期的值 boolean compareAndSet(int i, int expect, int update) //如果输入的数值等于预期值,则以原子方式将 index=i 位置的元素值设置为输入值(update) public final void lazySet(int i, int newValue)//最终 将index=i 位置的元素设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。 ``` **`AtomicIntegerArray` 类使用示例** : ```java import java.util.concurrent.atomic.AtomicIntegerArray; public class AtomicIntegerArrayTest { public static void main(String[] args) { int temvalue = 0; int[] nums = { 1, 2, 3, 4, 5, 6 }; AtomicIntegerArray i = new AtomicIntegerArray(nums); for (int j = 0; j < nums.length; j++) { System.out.println(i.get(j)); } temvalue = i.getAndSet(0, 2); System.out.println("temvalue:" + temvalue + "; i:" + i); temvalue = i.getAndIncrement(0); System.out.println("temvalue:" + temvalue + "; i:" + i); temvalue = i.getAndAdd(0, 5); System.out.println("temvalue:" + temvalue + "; i:" + i); } } ``` ## 引用类型原子类 基本类型原子类只能更新一个变量,如果需要原子更新多个变量,需要使用 引用类型原子类。 - `AtomicReference`:引用类型原子类 - `AtomicStampedReference`:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。 - `AtomicMarkableReference`:原子更新带有标记的引用类型。该类将 boolean 标记与引用关联起来,~~也可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。~~ 上面三个类提供的方法几乎相同,所以我们这里以 `AtomicReference` 为例子来介绍。 **`AtomicReference` 类使用示例** : ```java import java.util.concurrent.atomic.AtomicReference; public class AtomicReferenceTest { public static void main(String[] args) { AtomicReference < Person > ar = new AtomicReference < Person > (); Person person = new Person("SnailClimb", 22); ar.set(person); Person updatePerson = new Person("Daisy", 20); ar.compareAndSet(person, updatePerson); System.out.println(ar.get().getName()); System.out.println(ar.get().getAge()); } } class Person { private String name; private int age; public Person(String name, int age) { super(); this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } ``` 上述代码首先创建了一个 `Person` 对象,然后把 `Person` 对象设置进 `AtomicReference` 对象中,然后调用 `compareAndSet` 方法,该方法就是通过 CAS 操作设置 ar。如果 ar 的值为 `person` 的话,则将其设置为 `updatePerson`。实现原理与 `AtomicInteger` 类中的 `compareAndSet` 方法相同。运行上面的代码后的输出结果如下: ```plain Daisy 20 ``` **`AtomicStampedReference` 类使用示例** : ```java import java.util.concurrent.atomic.AtomicStampedReference; public class AtomicStampedReferenceDemo { public static void main(String[] args) { // 实例化、取当前值和 stamp 值 final Integer initialRef = 0, initialStamp = 0; final AtomicStampedReference asr = new AtomicStampedReference<>(initialRef, initialStamp); System.out.println("currentValue=" + asr.getReference() + ", currentStamp=" + asr.getStamp()); // compare and set final Integer newReference = 666, newStamp = 999; final boolean casResult = asr.compareAndSet(initialRef, newReference, initialStamp, newStamp); System.out.println("currentValue=" + asr.getReference() + ", currentStamp=" + asr.getStamp() + ", casResult=" + casResult); // 获取当前的值和当前的 stamp 值 int[] arr = new int[1]; final Integer currentValue = asr.get(arr); final int currentStamp = arr[0]; System.out.println("currentValue=" + currentValue + ", currentStamp=" + currentStamp); // 单独设置 stamp 值 final boolean attemptStampResult = asr.attemptStamp(newReference, 88); System.out.println("currentValue=" + asr.getReference() + ", currentStamp=" + asr.getStamp() + ", attemptStampResult=" + attemptStampResult); // 重新设置当前值和 stamp 值 asr.set(initialRef, initialStamp); System.out.println("currentValue=" + asr.getReference() + ", currentStamp=" + asr.getStamp()); // [不推荐使用,除非搞清楚注释的意思了] weak compare and set // 困惑!weakCompareAndSet 这个方法最终还是调用 compareAndSet 方法。[版本: jdk-8u191] // 但是注释上写着 "May fail spuriously and does not provide ordering guarantees, // so is only rarely an appropriate alternative to compareAndSet." // todo 感觉有可能是 jvm 通过方法名在 native 方法里面做了转发 final boolean wCasResult = asr.weakCompareAndSet(initialRef, newReference, initialStamp, newStamp); System.out.println("currentValue=" + asr.getReference() + ", currentStamp=" + asr.getStamp() + ", wCasResult=" + wCasResult); } } ``` 输出结果如下: ```plain currentValue=0, currentStamp=0 currentValue=666, currentStamp=999, casResult=true currentValue=666, currentStamp=999 currentValue=666, currentStamp=88, attemptStampResult=true currentValue=0, currentStamp=0 currentValue=666, currentStamp=999, wCasResult=true ``` **`AtomicMarkableReference` 类使用示例** : ```java import java.util.concurrent.atomic.AtomicMarkableReference; public class AtomicMarkableReferenceDemo { public static void main(String[] args) { // 实例化、取当前值和 mark 值 final Boolean initialRef = null, initialMark = false; final AtomicMarkableReference amr = new AtomicMarkableReference<>(initialRef, initialMark); System.out.println("currentValue=" + amr.getReference() + ", currentMark=" + amr.isMarked()); // compare and set final Boolean newReference1 = true, newMark1 = true; final boolean casResult = amr.compareAndSet(initialRef, newReference1, initialMark, newMark1); System.out.println("currentValue=" + amr.getReference() + ", currentMark=" + amr.isMarked() + ", casResult=" + casResult); // 获取当前的值和当前的 mark 值 boolean[] arr = new boolean[1]; final Boolean currentValue = amr.get(arr); final boolean currentMark = arr[0]; System.out.println("currentValue=" + currentValue + ", currentMark=" + currentMark); // 单独设置 mark 值 final boolean attemptMarkResult = amr.attemptMark(newReference1, false); System.out.println("currentValue=" + amr.getReference() + ", currentMark=" + amr.isMarked() + ", attemptMarkResult=" + attemptMarkResult); // 重新设置当前值和 mark 值 amr.set(initialRef, initialMark); System.out.println("currentValue=" + amr.getReference() + ", currentMark=" + amr.isMarked()); // [不推荐使用,除非搞清楚注释的意思了] weak compare and set // 困惑!weakCompareAndSet 这个方法最终还是调用 compareAndSet 方法。[版本: jdk-8u191] // 但是注释上写着 "May fail spuriously and does not provide ordering guarantees, // so is only rarely an appropriate alternative to compareAndSet." // todo 感觉有可能是 jvm 通过方法名在 native 方法里面做了转发 final boolean wCasResult = amr.weakCompareAndSet(initialRef, newReference1, initialMark, newMark1); System.out.println("currentValue=" + amr.getReference() + ", currentMark=" + amr.isMarked() + ", wCasResult=" + wCasResult); } } ``` 输出结果如下: ```plain currentValue=null, currentMark=false currentValue=true, currentMark=true, casResult=true currentValue=true, currentMark=true currentValue=true, currentMark=false, attemptMarkResult=true currentValue=null, currentMark=false currentValue=true, currentMark=true, wCasResult=true ``` ## 对象的属性修改类型原子类 如果需要原子更新某个类里的某个字段时,需要用到对象的属性修改类型原子类。 - `AtomicIntegerFieldUpdater`:原子更新整形字段的更新器 - `AtomicLongFieldUpdater`:原子更新长整形字段的更新器 - `AtomicReferenceFieldUpdater`:原子更新引用类型里的字段的更新器 要想原子地更新对象的属性需要两步。第一步,因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法 newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。第二步,更新的对象属性必须使用 public volatile 修饰符。 上面三个类提供的方法几乎相同,所以我们这里以 `AtomicIntegerFieldUpdater`为例子来介绍。 **`AtomicIntegerFieldUpdater` 类使用示例** : ```java import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; public class AtomicIntegerFieldUpdaterTest { public static void main(String[] args) { AtomicIntegerFieldUpdater a = AtomicIntegerFieldUpdater.newUpdater(User.class, "age"); User user = new User("Java", 22); System.out.println(a.getAndIncrement(user));// 22 System.out.println(a.get(user));// 23 } } class User { private String name; public volatile int age; public User(String name, int age) { super(); this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } ``` 输出结果: ```plain 22 23 ``` ## 参考 - 《Java 并发编程的艺术》