多线程使用会导致不安全,其中原子性就是一个不可以破坏的。原子性指的是一条指令不可以再被分割成多个操作,而是一起完成也就是要么全部执行成功要么全部执行失败。我们常见的不满足原子性的操作就是对共享变量进行 i++。通常我们使用synchronized 关键字来解决这个问题,在 JDK 1.5 中开始提供了
java.util.concurrent.atomic
包,这个包中的原子操作类提供了一种用法简单,性能高效的方式。如下图:
原子操作类
一、原子更新基本类型
原子更新基本类型的三个类:
- AtomicInteger
- AtomicBoolean
- AtomicLong
这三个类基本实现都是差不多的,我们以AtomicInteger
为例来看看它的一些常用方法:
方法 | 解释 |
---|---|
int addAndGet(int delta) |
将给定的值原子地与当前值相加,返回结果 |
boolean compareAndSet(int expect, int update) |
如果输入的值等于期望值,则将该值原子设置为给定的更新值。 |
int getAndIncrement() |
原子上给当前值 +1 |
int getAndDecrement() |
原子上给当前值 - 1 |
int getAndSet(int newValue) |
将原子设置为给定值并返回旧值。 |
这里我们选择的来看看int getAndIncrement()
是怎么实现的:
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
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;
}
实际上,就是调用了getAndAddInt
方法,得到旧值和新值后调用compareAndSwapInt
进行CAS直到设置成功,如果发现输入值不是期望值的时候就设置失败,然后进行循环,直到设值成功。
我们发现,只提供了这三种基本类型的更新,那么其它类型呢?float、char等等,又是怎么实现的呢?
Atomic包基本都是使用Unsafe实现的。
我们发现提供了三种方式,再看看AtomicBoolean源码:
这里我们发现实际上是把Boolean先转化成整型,接着再使用compareAndSwapInt
方法进行CAS,所以对于其它类型也可以通过这中方式来实现。
二、原子更新数组
原子更新数组的三个类:
- AtomicIntegerArray —— 原子更新整型数组中的元素
- AtomicLongArray —— 原子更新长整型数组中的元素
- AtomicReferenceArray —— 原子更新引用类型数组的元素
接着我们来看看AtomicIntegerArray
方法 | 解释 |
---|---|
int addAndGet(int i, int delta) |
以原子的方式将输入值与数组索引i的元素相加 |
boolean compareAndSet(int i, int expect, int update) |
如果输入的值等于期望值,以原子的方式将数组i位置的元素设置为新值 |
三、原子更新引用
原子更新引用类型的三个类:
- AtomicReference —— 原子更新引用类型
- AtomicReferenceFieldUpdater —— 原子更新引用类型的字段
- AtomicMarkableReference —— 原子更新带有标志位的引用类型
以AtomicReference
来看看它的方法:
方法 | 解释 |
---|---|
V getAndSet(V newValue) |
将原子设置为给定值并返回旧值。 |
boolean compareAndSet(int expect, int update) |
如果输入的值等于期望值,以原子的方式将元素设置为新值 |
四、原子更新属性
如果要原子的更新某个类里的某个字段是,就需要使用原子更新字段类,有以下三种:
- AtomicIntegerFieldUpdater —— 原子更新整型的字段
- AtomicLongFieldUpdater —— 原子更新长整型的字段
- AtomicStampedReference —— 原子跟新带有版本号的引用类型。
其中最后一种带有版本号的更新可以用来解决使用CAS进行原子更新可能出现的ABA问题。
想要原子更新字段需要两步,第一步:因为原子跟新字段类都是抽象类,每次使用时必须使用静态方法new Updater()
创建一个更新器,并且需要设置想要更新的类和属性。第二步就是更新的字段必须使用public volatile
来修饰。
方法 | 解释 |
---|---|
abstract boolean compareAndSet(T obj, int expect, int update) |
如果当前值为预期值,则将由此更新程序管理的给定对象的字段原子设置为给定的更新值。 |
int getAndSet(T obj, int newValue) |
将由此更新程序管理的给定对象的字段原子设置为给定值,并返回旧值。 |
int getAndAdd(T obj, int delta) |
将给定值原子地添加到由此更新程序管理的给定对象的字段的当前值。 |
其实可以看出,不管是哪种,基本的提供的方法都是相似的,所以掌握一个类,其它的也就触类旁通了。
文章参考《Java并发编程的艺术》一书,很推荐大家看这本书。文章如果有什么问题也欢迎大家指正,希望自己表达的能对你有所帮助,也欢迎大家点赞关注一起进步!