Java中的原子类

在多线程读写共享变量的场景中,很容易出现数据竞争,导致数据不一致。Java提供了synchronized关键字和Lock接口来保证多线程对同步块的有序访问,但是这两种方式都需要隐式或者显式的获取锁,性能开销略大。Java的Atomic包提供了多个原子操作类,可以安全、高效、简单的实现在多线程场景下读写变量。Atomic包中的类基本上都是使用Unsafe实现的包装类。

AtomicInteger

包括诸多方法实现整型数据的原子操作。比如对于整数的递增操作i++,由于这一操作并不是原子的,所以即便使用volatile修饰也不能保证线程安全,这种场景就可以使用AtomicInteger的方法:

1
2
3
4
5
6
7
8
/**
* Atomically increments by one the current value.
*
* @return the updated value
*/
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

以下通过AtomitInteger的addAndGet方法来分析这一原子类是如何实现线程安全的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* Atomically adds the given value to the current value.
*
* @param delta the value to add
* @param valueOffset the value memory address.
* @return the updated value
*/
public final int addAndGet(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}

public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

return var5;
}

var5是通过native方法取得的当前变量的值,compareAndSwapInt通过原子操作,将预期的的结果var5+var4替换为当前变量的值var5,如果方法成功就,compareAndSwapInt返回ture,循环结束,如果返回false,说明有其他线程在这段时间修改了当前变量的值,会重新通过循环获取var5,继续重试。

AtomicBoolean

实现原理与AtomicInteger基本相同,核心思想是将true和false映射为1和0。int类型的value存储的就是当前AtomicBoolean的状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
private volatile int value;

/**
* Creates a new {@code AtomicBoolean} with the given initial value.
*
* @param initialValue the initial value
*/
public AtomicBoolean(boolean initialValue) {
value = initialValue ? 1 : 0;
}

/**
* Atomically sets to the given value and returns the previous value.
*
* @param newValue the new value
* @return the previous value
*/
public final boolean getAndSet(boolean newValue) {
boolean prev;
do {
prev = get();
} while (!compareAndSet(prev, newValue));
return prev;
}

public final boolean compareAndSet(boolean expect, boolean update) {
int e = expect ? 1 : 0; //将boolean类型映射为int类型
int u = update ? 1 : 0;
return unsafe.compareAndSwapInt(this, valueOffset, e, u);//通过原子操作更新当前值。
}

原子更新数组

更新数组的值是通过调用其他原子更新基本类型或者引用类型来实现的,重点在于通过数组下标获得当前值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public final int updateAndGet(int i, IntUnaryOperator updateFunction) {
long offset = checkedByteOffset(i);//得到下标在内存中的地址
int prev, next;
do {
prev = getRaw(offset);
next = updateFunction.applyAsInt(prev);//转为int
} while (!compareAndSetRaw(offset, prev, next));
return next;
}

//在内存中取得当前值
private int getRaw(long offset) {
return unsafe.getIntVolatile(array, offset);
}

//以原子方式修改数组偏移量的值
private boolean compareAndSetRaw(long offset, int expect, int update) {
return unsafe.compareAndSwapInt(array, offset, expect, update);
}

原子更新引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package longkai;

import java.util.concurrent.atomic.AtomicReference;

public class AtomicReferenceTest {
public static void main(String[] args) {
AtomicReference<User> atomicReference = new AtomicReference<User>();
User firstUser = new User("long", 23);
atomicReference.set(firstUser);
User secondUser = new User("kai", 24);
atomicReference.compareAndSet(firstUser, secondUser);
System.out.println(atomicReference.get().getName());
System.out.println(atomicReference.get().getAge());

}
}

class User {
private String name;
private int age;

public User(String name, int age) {
this.name = name;
this.age = age;
}

public int getAge() {
return age;
}

public String getName() {
return name;
}
}

public final boolean compareAndSet(V expect, V update) {
return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}

实现方式

通过查看源码,最后发现Unfase类只提供了三种CAS方法:

1
2
3
4
5
6
7
8
9
/**
* 如果当前值为var4,则将值更新为var5
*/
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var5);

与AtomicBoolean类似,原子更新char、short、float、double可以实现。CAS算法是一种乐观锁的实现思想,在更新变量之前不对更新操作进行加锁,而是在更新之后再去看当前变量内存地址的值有没有发生改变,如果没有,就将希望更新的值写入到内存,如果发生了更改,则继续重试。

文章作者: hohnor
文章链接: http://www.zhulk3.cn/2022/02/10/Java中的原子类/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 甜茶不贵