通过上一部分的分析,我们应该基本理解了 CAS 的无锁思想,并对"魔法类" Unsafe 有了更全面的了解。这也是我们分析原子包的前提。
接下来,让我们一步步分析 CAS 在 Java 中的应用。JDK5 之后,JUC 包提供了 java.util.concurrent.atomic包,该包下提供了大量基于 CAS 的原子操作类。如果在未来你不希望对程序代码加锁,但又想避免线程安全问题,那么可以使用该包下提供的类。Atomic 包提供的操作类主要可以分为以下四种类型:
- 基本类型原子操作类
- 引用类型原子操作类
- 数组类型原子操作类
- 属性更新原子操作类
1. 基本类型原子操作类
Atomic 包提供的基本类型原子操作类有 AtomicInteger、AtomicBoolean 和 AtomicLong。它们的底层实现方式和使用方式是一样的。
所以我们只以其中一个------AtomicInteger 为例进行分析。AtomicInteger 主要用于对 int 类型的数据执行原子操作。它提供了原子自增方法、原子自减方法以及原子赋值方法等 API。
这里结合源码,我提供了一些注释:
csharp
public class AtomicInteger extends Number implements java.io.Serializable {
privatestaticfinallong serialVersionUID = 6214790243416807050L;
// 获取 Unsafe 类的实例
privatestaticfinal Unsafe unsafe = Unsafe.getUnsafe();
// 变量 value 在 AtomicInteger 实例对象内的内存偏移量
privatestaticfinallong valueOffset;
static {
try {
// 通过 Unsafe 类的 objectFieldOffset() 方法获取 value 变量在对象内存中的偏移量
// 通过这个偏移量 valueOffset,Unsafe 类的内部方法可以访问该变量 value 以进行读写操作
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { thrownew Error(ex); }
}
// 当前 AtomicInteger 封装的 int 变量 value
privatevolatileint value;
public...ment() {
return unsafe.getAndAddInt(this, valueOffset, -1);
}
// 当前值增加 delta 并返回旧值。底层 CAS 操作
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
// 当前值增加 1 并返回增加后的新值。底层 CAS 操作
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
// 当前值减少 1 并返回减少后的新值。底层 CAS 操作
public final int decrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
}
// 当前值增加 delta 并返回新值。底层 CAS 操作
public final int addAndGet(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}
// 省略了一些不常用的方法....
}
从上述源码可知,AtomicInteger 原子类的所有方法中,没有使用任何互斥锁机制来实现同步,而是通过前面介绍的 Unsafe 类提供的 CAS 操作来保证线程安全。在我们之前的分析文章中提到,count++ 实际上存在线程安全问题。那么让我们观察一下 AtomicInteger 原子类中的自增实现 incrementAndGet:
csharp
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
从上述源码可以看出,最终的实现是调用 unsafe.getAndAddInt()方法来完成的。正如我们在之前的分析中所学到的,这个方法是 JDK8 之后基于原始 CAS 操作的新方法。我们进一步跟进:
arduino
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
}
可以看出,getAndAddInt通过一个 do-while 死循环不断重试更新要设置的值,直到成功为止。其中调用了 Unsafe 类中的 compareAndSwapInt方法,这是一个 CAS 操作方法。注意:在 Java 8 之前的源码实现中,这里是一个 for 循环,并且自增逻辑是直接在 AtomicInteger 类中实现的。在 Java 8 中,它被替换为 while 循环并移动到了 Unsafe 类中实现。
下面让我们通过一个简单的小 Demo 来掌握基本类型原子操作类的用法:
ini
public class AtomicIntegerDemo {
static AtomicInteger atomicInteger = new AtomicInteger();
publicstaticclass AddThread implements Runnable {
public void run(){
for(int i = 0; i < 10000; i++)
atomicInteger.incrementAndGet();
}
}
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[11];
// 开启10个线程同时对 atomicInteger 进行自增操作
for(int i = 1; i <= 10; i++) {
threads[i]=new Thread(new AddThread());
}
for(int i = 1; i <= 10; i++) {
threads[i].start();
}
// 等待所有线程执行完毕再输出结果
for(int i = 1; i <= 10; i++) {
threads[i].join();
}
System.out.println(atomicInteger);
}
}
输出:100000
在上面的 Demo 中,使用了 AtomicInteger 代替了原始的 int。这样,无需加锁即可保证线程安全。AtomicBoolean 和 AtomicLong 就不再分析了,其用法和原理是一样的。
2. 引用类型原子操作类
引用类型的原子操作类主要分析 AtomicReference 类。其他的原理和用法也都一样。
让我们直接看一个例子:
typescript
public class AtomicReferenceDemo {
publicstatic AtomicReference<Student> atomicStudentRef = new AtomicReference<>();
public static void main(String[] args) {
Student s1 = new Student("Tom", 18);
atomicStudentRef.set(s1);
Student s2 = new Student("Jerry", 20);
atomicStudentRef.compareAndSet(s1, s2);
System.out.println(atomicStudentRef.get().toString());
}
@AllArgsConstructor
staticclass Student {
private String name;
privateint age;
@java.lang.Override
public java.lang.String toString() {
return"Student{" +
"name='" + name + ''' +
", age=" + age +
'}';
}
}
}
输出:
ini
Student{name='Jerry', age=20}
在此之前,我们分析了原子计数器 AtomicInteger 类的底层实现是通过 Unsafe 类中的 CAS 操作完成的。那么我们要分析的 AtomicReference 底层又是如何实现的呢?
typescript
public class AtomicReference<V> implements java.io.Serializable {
// 获取 Unsafe 对象实例
privatestaticfinal Unsafe unsafe = Unsafe.getUnsafe();
// 定义 value 偏移量
privatestaticfinallong valueOffset;
static {
try {
/* 静态代码块在类加载时为 offset 赋值。通过 Unsafe 类提供的 API 获取类属性的地址,从而获取当前类中定义的属性 value 的地址 */
valueOffset = unsafe.objectFieldOffset
(AtomicReference.class.getDeclaredField("value"));
} catch (Exception ex) { thrownew Error(ex); }
}
// 内部变量 value。Unsafe 类可以通过 valueOffset 内存偏移量获取该变量
privatevolatile V value;
/* 原子替换方法,间接调用 Unsafe 类的 compareAndSwapObject(),
这是一个实现了 CAS 操作的本地方法 */
public final boolean compareAndSet(V expect, V update) {
return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}
// 设置并获取旧值
public final V getAndSet(V newValue) {
return (V)unsafe.getAndSetObject(this, valueOffset, newValue);
}
// 省略代码......
}
// Unsafe 类中的 getAndSetObject 方法在调用时实际上仍然是一个 CAS 操作
public final Object getAndSetObject(Object o, long offset, Object newValue) {
Object v;
do {
v = getObjectVolatile(o, offset);
} while (!compareAndSwapObject(o, offset, v, newValue));
return v;
}
从上述源码可知,AtomicReference 与我们之前分析的 AtomicInteger 原理大体相同,最终也都是通过 Unsafe 类提供的 CAS 操作实现的。AtomicReference 其他方法的实现原理也大致相同。
不过,需要注意的是 Java 8 为 AtomicReference 增加了几个新的 API:
- getAndUpdate(UnaryOperator)
- updateAndGet(UnaryOperator)
- getAndAccumulate(V, AnaryOperator)
- accumulateAndGet(V, AnaryOperator)
上述方法几乎存在于所有原子类中,这些方法可以在执行 CAS 操作之前,基于 Lambda 表达式对期望值或要更新的值执行其他操作。简单来说,就是在执行 CAS 更新之前,对期望值或要更新的值进行额外的修改。
3. 数组类型原子操作类
数组类型原子操作类的含义其实很简单,指的是以原子的形式更新数组中的元素,以避免线程安全问题。JUC 包中的数组类型原子操作类具体分为以下三个类:
- AtomicIntegerArray
- AtomicLongArray
- AtomicReferenceArray
同样,我们只分析单个类,其他两个大致相同。这里我们以 AtomicIntegerArray 为例进行分析:
ini
public class AtomicIntegerArrayDemo {
static AtomicIntegerArray atomicArr = new AtomicIntegerArray(10);
publicstaticclass incrementTask implements Runnable {
public void run() {
for (int i = 0; i < 10000; i++) {
atomicArr.getAndIncrement(i % atomicArr.length());
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(new incrementTask());
}
for (int i = 0; i < 10; i++) {
threads[i].start();
}
for (int i = 0; i < 10; i++) {
threads[i].join();
}
System.out.println(atomicArr);
}
}
输出:
csharp
[10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000]
在上面的 Demo 中,我们开启了 10 个线程对数组中的元素执行自增操作,执行结果符合我们的预期。接下来让我们一步步分析 AtomicIntegerArray 的内部实现逻辑。源码如下:
csharp
public class AtomicIntegerArray implements java.io.Serializable {
// 获取 Unsafe 实例对象
privatestaticfinal Unsafe unsafe = Unsafe.getUnsafe();
// 正如我们之前在 Unsafe 类中分析的那样,arrayBaseOffset() 的功能是:获取数组第一个元素的内存起始地址
privatestaticfinalint base = unsafe.arrayBaseOffset(int[].class);
privatestaticfinalint shift;
// 内部数组
privatefinalint[] array;
static {
// 获取数组中一个元素占用的内存空间
int scale = unsafe.arrayIndexScale(int[].class);
// 检查是否为 2 的幂次方,否则抛出异常
if ((scale & (scale - 1))!= 0)
thrownew Error("data type scale not a power of two");
//
shift = 31 - Integer.numberOfLeadingZeros(scale);
}
private long checkedByteOffset(int i) {
if (i < 0 || i >= array.length)
thrownew IndexOutOfBoundsException("index " + i);
return byteOffset(i);
}
// 计算数组中每个元素的内存地址
private static long byteOffset(int i) {
return ((long) i << shift) + base;
}
// 省略代码......
}
在之前对 Unsafe 的分析中,我们了解到 arrayBaseOffset可以获取数组中第一个元素的内存起始位置,我们还有一个 API:arrayIndexScale可以获取数组中指定下标元素占用的内存空间大小。目前,AtomicIntegerArray 是 int 类型的。我们在学习 Java 基础时知道,int 的大小在内存中占用 4 个字节,所以 scale 的值为 4。那么现在,如果我们根据这两条信息来计算数组中每个元素的内存起始地址呢?稍微推导一下,我们可以得出以下结论:
数组中每个元素的内存起始位置 = 数组第一个元素的起始位置 + 数组元素的下标 _ 数组中每个元素占用的内存空间
而上述源码中的 byteOffset(i)方法就是上述公式的体现。有些人看到这里可能会感到困惑。这个方法的实现似乎与我刚才描述的结论不一致。别担心,让我们先看看 shift的值是如何获取的:
shift = 31 --- Integer.numberOfLeadingZeros(scale);
Integer.numberOfLeadingZeros(scale):计算 scale 的前导零数量(转换为二进制后前面连续 0 的个数称为前导零)。scale = 4,转换为二进制是 00000000 00000000 00000000 00000100,所以前导零数量是 29,因此 shift 值为 2。
那么回到我们需要解决的问题:如何计算数组中每个元素的内存起始位置?我们利用刚刚得到的 shift 值,将其应用到 byteOffset(i)方法中,来计算数组中每个元素的内存起始位置(前提是数组下标不越界):
byteOffset 方法体:(i << shift) + base (i 是索引,即数组元素的下标,base 是数组第一个元素的内存起始位置)
第一个元素:memoryAddress = 0 << 2 + base,即 memoryAddress = base + 0 * 4第二个元素:memoryAddress = 1 << 2 + base,即 memoryAddress = base + 1 * 4
第三个元素:memoryAddress = 2 << 2 + base,即 memoryAddress = base + 2 * 4
第四个元素:memoryAddress = 3 << 2 + base,即 memoryAddress = base + 3 * 4
其他元素的位置以此类推......
以上描述就是 AtomicIntegerArray.byteOffset方法的原理。所以 byteOffset(int)方法可以根据数组下标计算每个元素的内存地址。
AtomicIntegerArray 中的其他方法都是通过间接调用 Unsafe 类的 CAS 原子操作方法实现的。让我们简单看几个常用的方法:
arduino
// 执行自增操作并返回旧值。入参 i 是索引,即数组元素的下标
public final int getAndIncrement(int i) {
return getAndAdd(i, 1);
}
// 对指定下标的元素执行自增操作并返回新值
public final int incrementAndGet(int i) {
return getAndAdd(i, 1) + 1;
}
// 对指定下标的元素执行自减操作并返回新值
public final int decrementAndGet(int i) {
return getAndAdd(i, -1) - 1;
}
// 间接调用 unsafe.getAndAddInt() 方法
public final int getAndAdd(int i, int delta) {
return unsafe.getAndAddInt(array, checkedByteOffset(i), delta);
}
// Unsafe 类中的 getAndAddInt 方法执行 CAS 操作
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
}
4. 属性更新原子操作类
如果我们只需要某个类的某个字段(类的属性)也变成原子操作,我们可以使用属性更新原子操作类。
例如,某时刻项目需求发生变化,然后需求再次变更,导致某个类中的变量需要多线程操作。由于这个变量在多处使用,修改起来比较麻烦,而且原本使用的地方不需要线程安全,只有新场景需要使用。我们可以借助原子更新器来处理这种场景。Atomic 并发包提供了以下三个类:
- AtomicIntegerFieldUpdater:用于更新整型属性的原子操作类
- AtomicLongFieldUpdater:用于更新长整型属性的原子操作类
- AtomicReferenceFieldUpdater:用于更新引用类型中属性的原子操作类
但是,值得注意的是,使用原子更新类的条件比较严格,如下:
- 被操作的字段不能被 static 修饰。
- 被操作的字段不能被 final 修饰,因为常量无法修改。
- 被操作的字段必须被 volatile 修饰以保证可见性,即必须保证数据的读取是线程可见的。
- 属性必须对 Updater 所在的当前区域可见。如果原子更新器操作不在当前类中执行,则不能使用 private 和 protected 修饰符。当子类操作父类时,修饰符必须是 protected 权限或以上。如果在同一个包中,必须是 default 权限或以上。也就是说,要时刻保证操作类与被操作类之间的可见性。
让我们看一下 AtomicIntegerFieldUpdater 和 AtomicReferenceFieldUpdater 的简单使用方法:
typescript
import lombok.AllArgsConstructor;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
publicclass AtomicIntegerFieldUpdaterDemo {
publicstaticclass Candidate {
int id;
volatileint score;
}
static AtomicIntegerFieldUpdater<Candidate> atIntegerUpdater
= AtomicIntegerFieldUpdater.newUpdater(Candidate.class, "score");
static AtomicReferenceFieldUpdater<Student, String> atRefUpdate =
AtomicReferenceFieldUpdater.newUpdater(Student.class, String.class, "name");
// 用于验证分数是否正确
publicstatic AtomicInteger allScore = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
final Candidate stu = new Candidate();
Thread[] t = new Thread[10000];
// 开启 10,000 个线程
for (int i = 0; i < 10000; i++) {
t[i] = new Thread() {
public void run() {
if (Math.random() > 0.5) {
atIntegerUpdater.incrementAndGet(stu);
allScore.incrementAndGet();
}
}
};
t[i].start();
}
for (int i = 0; i < 10000; i++) {
t[i].join();
}
System.out.println("final score=" + stu.score);
System.out.println("check all score=" + allScore);
// AtomicReferenceFieldUpdater 简单使用
Student student = new Student("Jerry", 17);
atRefUpdate.compareAndSet(student, student.name, "Jerry-1");
System.out.println(student);
}
@AllArgsConstructor
staticclass Student {
private String name;
privateint age;
@java.lang.Override
public java.lang.String toString() {
return"Student{" +
"name='" + name + ''' +
", age=" + age +
'}';
}
}
}
我们使用 AtomicIntegerFieldUpdater 来更新候选人(Candidate)的分数。启动 10,000 个线程进行投票。当随机值大于 0.5 时,算作一票,分数自增一次。其中,allScore 用于验证分数是否正确(实际用于验证 AtomicIntegerFieldUpdater 更新的字段是否线程安全)。当 allScore 与 score 相同时,说明投票结果正确,也代表 AtomicIntegerFieldUpdater 能正确更新字段 score 的值并且是线程安全的。
对于 AtomicReferenceFieldUpdater,我们在代码中简单演示了它的使用方法。注意在指定 AtomicReferenceFieldUpdater 的泛型时,需要两个泛型参数,一个是修改类的类型,另一个是修改字段的类型。
至于 AtomicLongFieldUpdater,它与 AtomicIntegerFieldUpdater 类似,不再赘述。接下来简单了解一下 AtomicIntegerFieldUpdater 的实现原理。它实际上是反射与 Unsafe 类的结合。AtomicIntegerFieldUpdater 是一个抽象类,实际的实现类是 AtomicIntegerFieldUpdaterImpl:
csharp
public abstract class AtomicIntegerFieldUpdater<T> {
public static <U> AtomicIntegerFieldUpdater<U> newUpdater(Class<U> tclass, String fieldName){
return new AtomicIntegerFieldUpdaterImpl<U>
(tclass, fieldName, Reflection.getCallerClass());
}
}
不难从源码中发现,实际上 AtomicIntegerFieldUpdater 的定义是一个抽象类,最终的实现类是 AtomicIntegerFieldUpdaterImpl。
让我们进一步查看 AtomicIntegerFieldUpdaterImpl 的源码。我提供了一些注释:
scss
private staticclass AtomicIntegerFieldUpdaterImpl<T>
extends AtomicIntegerFieldUpdater<T> {
// 获取 unsafe 实例
privatestaticfinal Unsafe unsafe = Unsafe.getUnsafe();
// 定义内存偏移量
privatefinallong offset;
privatefinal Class<T> tclass;
privatefinal Class<?> cclass;
// 构造方法
AtomicIntegerFieldUpdaterImpl(final Class<T> tclass,
final String fieldName,
final Class<?> caller) {
final Field field;// 要修改的字段
finalint modifiers;// 字段修饰符
try {
field = AccessController.doPrivileged(
new PrivilegedExceptionAction<Field>() {
public Field run() throws NoSuchFieldException {
// 通过反射获取 Field 对象
return tclass.getDeclaredField(fieldName);
}
});
// 获取字段修饰符
modifiers = field.getModifiers();
// 检查字段的访问权限,如果不在访问范围内则抛出异常。
sun.reflect.misc.ReflectUtil.ensureMemberAccess(
caller, tclass, null, modifiers);
// 获取对应类对象的类加载器
ClassLoader cl = tclass.getClassLoader();
ClassLoader ccl = caller.getClassLoader();
if ((ccl != null) && (ccl != cl) &&
((cl == null) || !isAncestor(cl, ccl))) {
sun.reflect.misc.ReflectUtil.checkPackageAccess(tclass);
}
} catch (PrivilegedActionException pae) {
thrownew RuntimeException(pae.getException());
} catch (Exception ex) {
thrownew RuntimeException(ex);
}
Class<?> fieldt = field.getType();
// 判断类型是否为 int
if (fieldt != int.class)
throw new IllegalArgumentException("Must be integer type");
// 判断是否被 volatile 修饰
if (!Modifier.isVolatile(modifiers))
thrownew IllegalArgumentException("Must be volatile type");
this.cclass = (Modifier.isProtected(modifiers) &&
caller != tclass) ? caller : null;
this.tclass = tclass;
// 获取字段在对象内存中的偏移量,并使用内存偏移量获取或修改字段的值。
offset = unsafe.objectFieldOffset(field);
}
}
从 AtomicIntegerFieldUpdaterImpl 的源码可以看出,实际上字段更新器都是通过反射机制和 Unsafe 类实现的。
让我们看一下 AtomicIntegerFieldUpdaterImpl 中自增方法 incrementAndGet 的实现:
csharp
public int incrementAndGet(T obj) {
int prev, next;
do {
prev = get(obj);
next = prev + 1;
// 调用下面的 compareAndSet 方法完成 cas 操作
} while (!compareAndSet(obj, prev, next));
return next;
}
// 最终调用的是 Unsafe 类的 compareAndSwapInt() 方法。
public boolean compareAndSet(T obj, int expect, int update) {
if (obj == null || obj.getClass() != tclass || cclass != null) fullCheck(obj);
return unsafe.compareAndSwapInt(obj, offset, expect, update);
}
我们在这里可以发现,实际上 J.U.C 中 Atomic 包下原子类的最终实现都是通过前面分析过的 Unsafe 类完成的。包括后面我们要讲到的许多并发知识,如 AQS 等,都会涉及到 CAS 机制。敬请期待~