概述
java.util.concurrent.atomic 包一共提供了 13 个类,属于 4 种类型的原子更新方式:原子更新基本数据类型、原子更新数组、原子更新引用、原子更新属性
原子更新基本类型
java.util.concurrent.atomic 包提供了以下 3 个类:
- AtomicBoolean:原子更新布尔类型
- AtomicInteger : 原子更新整型
- AtomicLong:原子更新整型
以上三个类提供的方法几乎一样,下面只分析 AtomicInteger:
- int addAndGet(int delta):以原子方式将输入的数值与实例中的数值(value)相加,并返回结果
- boolean compareAndSet(int expect,int update):如果输入的数值等于预期值,则以原子方式将该值设置为输入的值
- int getAndIncrement():以原子方式将当前值加 1,但是,返回的是自增前的值
- void lazySet(int newValue):最终会设置成 newValue,使用 lazySet 设置值后,可能导致其线程在之后的一小段时间内还是可以读到旧的值
- int getAndSet(int newValue):以原子方式设置为 newValue 的值,并返回旧值
通过 getAndIncrement 方法来看看实现原理:
public final int getAndIncrement() {
for (;;) {
int current = get(); // 先取得 AtomicInteger 存储的数值
int next = current + 1; // 对当前数组加 1
// CAS 操作更新,先检查当前数值是否等于 current,如果是则将 AtomicInteger 的当前数值更新成 next;如果不是,则返回 false,重新循环更新
if (compareAndSet(current, next))
return current; // 返回更新前的值
}
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
如何原子地更新其他的基本类型?
由于 java.util.concurrent.atomic 包的类都是使用 Unsafe 实现的,先看一下 Unsafe 的源码:
public native boolean compareAndSwapObject(Object obj, long offset,Object expect, Object update);
public native boolean compareAndSwapLong(Object obj, long offset,long expect, long update);
public native boolean compareAndSwapInt(Object obj, long offset,int expect, int update);
通过 Unsafe 源码分析可知,只提供了 3 种 CAS 操作,分别是 compareAndSwapObject、compareAndSwapLong、compareAndSwapInt
我们再看一下 AtomicBoolean 类的实现:
public final boolean compareAndSet(boolean expect, boolean update) {
int e = expect ? 1 : 0; // 转换成 int 类型
int u = update ? 1 : 0; // 转换成 int 类型
return unsafe.compareAndSwapInt(this, valueOffset, e, u);
}
通过以上代码可以发现,AtomicBoolean 先把 boolean 类型的参数转换成 int 类型,然后再调用 Unsafe 的 compareAndSwapInt 来进行 CAS 操作。因此,对于 char、float、double 类型的变量也可以用类似的思路实现
原子更新数组
java.util.concurrent.atomic 包提供了 3 个原子更新数组的类:
- AtomicIntegerArray:原子更新整型数组里的元素
- AtomicLongArray:原子更新长整型数组里的元素
- AtomicReferenceArray:原子更新引用类型数组里的元素
AtomicIntegerArray 常用方法如下:
- addAndGet(int i,int delta):以原子方式将输入值与数组中索引i的元素相加。
- boolean compareAndSet(int i, int expect, int update):如果当前值等于预期值,则以原子方式将数组位置 i 的元素设置成 update 值
public class Test {
static int[] value = new int[] {1,2};
static AtomicIntegerArray ai = new AtomicIntegerArray(value);
public static void main(String[] args){
ai.getAndSet(0, 3);
System.out.println(ai.get(0));
System.out.println(value[0]);
}
}
输出结果为:
3
1
需要注意的是,数组 value 通过构造方法传递进去,然后 AtomicIntegerArray 会将当前数组复制一份,所以当 AtomicIntegerArray 对内部数组元素进行修改,不会影响传入的数组
原子更新引用类型
如果要原子更新多个变量,需要使用原子更新引用类型提供的类。java.util.concurrent.atomic 包提供了 3 个类:
- AtomicReference:原子更新引用类型
- AtomicReferenceFieldUpdater:原子更新引用类型里的字段
- AtomicMarkableReference:原子更新带有标记为的引用类型。可以原子更新一个布尔类型的标记位和引用类型。构造方法是 AtomicMarkableReference(V initialRef, boolean initialMark)
AtomicReference 示例如下:
public class AtomicReferenceTest {
static class User {
private String name;
private int old;
public User(String name,int old){
this.name = name;
this.old = old;
}
public String getName() {
return name;
}
public int getOld() {
return old;
}
}
public static AtomicReference atomicReference = new AtomicReference();
public static void main(String[] args){
AtomicReferenceTest.User user = new User("Tom", 15);
atomicReference.set(user);
User updateUser = new User("Jack",16);
atomicReference.compareAndSet(user, updateUser);
System.out.println(atomicReference.get().getName());
System.out.println(atomicReference.get().getOld());
}
}
输出结果:
jack
16
原子更新字段类
如果需要原子更新某个类的字段时,需要使用原子更新字段类,java.util.concurrent.atomic 提供了3个类:
- AtomicIntegerFieldUpdater:原子更新整型的字段的更新器
- AtomicLongFieldUpdater:原子更新长整型的字段的更新器
- AtomicStampedReference:原子更新带有版本号的引用类型。该类型将整数值与引用关联起来,可用于原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题
原子更新类的字段,需要两步:
- 第一步:因为原子更新字段类都是抽象类,每次使用的时候必须使用静态方法 newUpdater() 创建一个更新器,并且需要设置想要更新的类和属性
- 第二步:更新类的字段必须使用 public volatile 修饰
AtomicIntegerFieldUpdater 示例如下:
public class AtomicIntegerFieldUpdaterTest {
static class User {
private String name;
private int old;
public User(String name, int old){
this.name = name;
this.old = old;
}
public String getName() {
return name;
}
public int getOld() {
return old;
}
}
private static AtomicIntegerFieldUpdater aifu = AtomicIntegerFieldUpdater.newUpdater(User.class, "old");
public static void main(String[] args){
User user = new User("Tom", 15);
System.out.println(aifu.getAndIncrement(user)); // old 加 1,但是仍然会输出 15
System.out.println(aifu.get(user)); // 16
}
}
输入结果如下;
15
16