背景
我们知道 volatile 关键字虽然轻量级,但不能保证原子性,synchronized 可以保证原子性,但是比较重量级。
那么有没有一种简单的、性能高的方法来保证 Java 的原子操作呢?答案当然是有的。
在 JDK1.5 时期, Java 家族加入了 Atomic 包。
原子类(Atomic Classes)
Java 中提供了对应的原子操作类来实现原子地且有条件进行的读-改-写操作操作。其本质是利用了 CPU 级别的原子指令。由于原子类是基于 CPU 级别的指令,因此其开销比需要操作系统参与的锁的开销小。
CAS 算法
CAS (比较与交换,Compare and swap) 算法是一种非阻塞算法(non-blocking algorhithm),同时也是一种无锁算法(lock-free algorhithm),基于乐观并发(pessimistic concurrent control)的思想。
非阻塞版本相对于基于锁的版本有几个性能优势。首先,它用硬件的原生形态代替 JVM 的锁定代码路径,从而在更细的粒度层次上(独立的内存位置)进行同步,失败的线程也可以立即重试,而不会被挂起后重新调度。更细的粒度降低了争用的机会,不用重新调度就能重试的能力也降低了争用的成本。即使有少量失败的 CAS 操作,这种方法仍然会比由于锁争用造成的重新调度快得多。
以 Atomic 类中的 incrementAndGet()
方法为例,其内部就调用了Unsafe中的 native 方法(CompareAndSet)以实现递增数值:
private volatile int value;
public final int get() {
return value;
}
/**
* Atomically increments by one the current value.
*
* @return the updated value
*/
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
我们来分析下incrementAndGet的逻辑:
- 先获取当前的value值
- 对value加一
- 第三步是关键步骤,调用compareAndSet方法来进行一个原子更新操作,这个方法的语义是:先检查当前value是否等于current,如果相等,则意味着value没被其他线程修改过,更新并返回true。如果不相等,compareAndSet则会返回false,然后循环继续尝试更新。
AtomicInteger 例子
比如,AtomicInteger 表示一个 int 类型的值,并提供了 get 和 set 方法,这些 Volatile 类型的 int变量在读取和写入上有着相同的内存语义。它还提供了一个原子的 compareAndSet 方法(如果该方法成功执行,那么将实现与读取/写入一个 volatile 变量相同的内存效果),以及原子地添加、递增和递减等方法。
AtomicInteger 的使用方法如下:
AtomicInteger atomicInteger = new AtomicInteger();
for(int b = 0; b < numThreads; b++) {
new Thread(() -> {
for(int a = 0; a < iteration; a++) {
atomicInteger.incrementAndGet();
}
}).start();
}
原子基本类型
原子基本类型,从名称上就可以看出,是为基本类型提供原子操作的类。它们是以下3位:
- AtomicBoolean
- AtomicInteger
- AtomicLong
我们来看看 AtomicInteger 中的一些常用方法。
- int getAndSet(int newValue):以原子方式更新,并且返回旧值。
- int getAndIncrement():以原子方式自增,返回的是自增前的值。
- int addAndGet(int delta):以原子方式,将当前值与输入值相加,返回的是计算后的值。
原子数组
下面的类是为数组中某个元素的更新提供原子操作的类。
- AtomicIntegerArray
- AtomicLongArray
- AtomicReferenceArray
我们来看看 AtomicIntegerArray 中的一些常用方法:
- int getAndSet(int i, int newValue):更新对应位置的值,返回更新前的值。
- boolean compareAndSet(int i, int expect, int update):比较对应位置的值与期望值,如果相等,则更新,返回true。如果不能返回false。
- int getAndIncrement(int i):对位置i的元素以原子方式自增,返回更新前的值。
- int getAndAdd(int i, int delta):对位置i的元素以原子方式计算,返回更新前的值。
原子引用类型
如果我们只需要某个类里的某个字段,也就是说让普通的变量也享受原子操作,可以使用原子更新字段类,如在某些时候由于项目前期考虑不周全,项目需求又发生变化,使得某个类中的变量需要执行多线程操作,由于该变量多处使用,改动起来比较麻烦,而且原来使用的地方无需使用线程安全,只要求新场景需要使用时,可以借助原子更新器处理这种场景,Atomic并发包提供了以下三个类:
- AtomicReference
- AtomicReferenceFieldUpdater
- FieldUpdater方便以原子方式更新对象中的字段,字段不需要声明为原子变量,FieldUpdater是基于反射机制实现的。
- AtomicMarkableReference
原子更新字段类
前文提到了AtomicReferenceFieldUpdater类,它更新的是类的字段,除了这个类,Atomic还提供了另外三个类用于更新类中的字段:
- AtomicIntegerFieldUpdater
- AtomicLongFieldUpdater
- AtomicStampedReference
Reference
- Java原子操作类,知多少? - https://zhuanlan.zhihu.com/p/42908787
- Java atomic原子类的使用方法和原理(一) - https://www.jianshu.com/p/a2f3c46d4783
- 非阻塞算法简介 - https://www.ibm.com/developerworks/cn/java/j-jtp04186/
- Java并发编程-无锁CAS与Unsafe类及其并发包Atomic - https://juejin.im/entry/595c599e6fb9a06bc6042514
- Java原子类实现原理分析 - https://www.cnblogs.com/chengxiao/p/6789109.html