跳到主要内容 Java 并发编程:JUC 包中原子操作类的原理和用法 | 极客日志
Java java 算法
Java 并发编程:JUC 包中原子操作类的原理和用法 Java JUC 包提供了一系列基于 CAS 机制的原子操作类,涵盖基本类型、引用类型、数组类型及字段更新器。文章通过分析 AtomicInteger、AtomicReference 等核心类的源码,展示了如何利用 Unsafe 类实现无锁线程安全。内容包含各类 API 用法演示、底层内存偏移量计算逻辑以及原子更新器的反射与 CAS 结合原理,旨在帮助开发者在不使用互斥锁的前提下解决多线程并发数据安全问题。
2177283801 发布于 2026/2/9 更新于 2026/4/18 1 浏览通过上一部分的分析,我们应该基本理解了 CAS 的无锁思想,并对'魔法类'Unsafe 有了更全面的了解。这也是我们分析原子包的前提。
接下来,让我们一步步分析 CAS 在 Java 中的应用。JDK5 之后,JUC 包提供了 java.util.concurrent.atomic 包,该包下提供了大量基于 CAS 的原子操作类。如果在未来你不希望对程序代码加锁,但又想避免线程安全问题,那么可以使用该包下提供的类。Atomic 包提供的操作类主要可以分为以下四种类型:
基本类型原子操作类
引用类型原子操作类
数组类型原子操作类
属性更新原子操作类
1. 基本类型原子操作类
Atomic 包提供的基本类型原子操作类有 AtomicInteger、AtomicBoolean 和 AtomicLong。它们的底层实现方式和使用方式是一样的。
所以我们只以其中一个——AtomicInteger 为例进行分析。AtomicInteger 主要用于对 int 类型的数据执行原子操作。它提供了原子自增方法、原子自减方法以及原子赋值方法等 API。
这里结合源码,我提供了一些注释:
public class AtomicInteger extends Number implements java .io.Serializable {
private static final long serialVersionUID = 6214790243416807050L ;
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset(
AtomicInteger.class.getDeclaredField("value" ));
} catch (Exception ex) { throw (ex); }
}
value;
{
unsafe.getAndAddInt( , valueOffset, - );
}
{
unsafe.getAndAddInt( , valueOffset, delta);
}
{
unsafe.getAndAddInt( , valueOffset, ) + ;
}
{
unsafe.getAndAddInt( , valueOffset, - ) - ;
}
{
unsafe.getAndAddInt( , valueOffset, delta) + delta;
}
}
微信扫一扫,关注极客日志 微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
new
Error
private
volatile
int
public
int
getAndDecrement
()
return
this
1
public
final
int
getAndAdd
(int delta)
return
this
public
final
int
incrementAndGet
()
return
this
1
1
public
final
int
decrementAndGet
()
return
this
1
1
public
final
int
addAndGet
(int delta)
return
this
从上述源码可知,AtomicInteger 原子类的所有方法中,没有使用任何互斥锁机制来实现同步,而是通过前面介绍的 Unsafe 类提供的 CAS 操作来保证线程安全。在我们之前的分析文章中提到,count++ 实际上存在线程安全问题。那么让我们观察一下 AtomicInteger 原子类中的自增实现 incrementAndGet:
public final int incrementAndGet () {
return unsafe.getAndAddInt(this , valueOffset, 1 ) + 1 ;
}
从上述源码可以看出,最终的实现是调用 unsafe.getAndAddInt() 方法来完成的。正如我们在之前的分析中所学到的,这个方法是 JDK8 之后基于原始 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;
}
可以看出,getAndAddInt 通过一个 do-while 死循环不断重试更新要设置的值,直到成功为止。其中调用了 Unsafe 类中的 compareAndSwapInt 方法,这是一个 CAS 操作方法。注意:在 Java 8 之前的源码实现中,这里是一个 for 循环,并且自增逻辑是直接在 AtomicInteger 类中实现的。在 Java 8 中,它被替换为 while 循环并移动到了 Unsafe 类中实现。
下面让我们通过一个简单的小 Demo 来掌握基本类型原子操作类的用法:
public class AtomicIntegerDemo {
static AtomicInteger atomicInteger = new AtomicInteger ();
public static class 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 ];
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);
}
}
在上面的 Demo 中,使用了 AtomicInteger 代替了原始的 int。这样,无需加锁即可保证线程安全。AtomicBoolean 和 AtomicLong 就不再分析了,其用法和原理是一样的。
2. 引用类型原子操作类 引用类型的原子操作类主要分析 AtomicReference 类。其他的原理和用法也都一样。
public class AtomicReferenceDemo {
public static 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());
}
@lombok .AllArgsConstructor
static class Student {
private String name;
private int age;
@java .lang.Override
public java.lang.String toString () {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}' ;
}
}
}
Student{name='Jerry', age=20}
在此之前,我们分析了原子计数器 AtomicInteger 类的底层实现是通过 Unsafe 类中的 CAS 操作完成的。那么我们要分析的 AtomicReference 底层又是如何实现的呢?
public class AtomicReference <V> implements java .io.Serializable {
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset(
AtomicReference.class.getDeclaredField("value" ));
} catch (Exception ex) { throw new Error (ex); }
}
private volatile V value;
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);
}
}
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 为例进行分析:
public class AtomicIntegerArrayDemo {
static AtomicIntegerArray atomicArr = new AtomicIntegerArray (10 );
public static class 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);
}
}
[10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000]
在上面的 Demo 中,我们开启了 10 个线程对数组中的元素执行自增操作,执行结果符合我们的预期。接下来让我们一步步分析 AtomicIntegerArray 的内部实现逻辑。源码如下:
public class AtomicIntegerArray implements java .io.Serializable {
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final int base = unsafe.arrayBaseOffset(int [].class);
private static final int shift;
private final int [] array;
static {
int scale = unsafe.arrayIndexScale(int [].class);
if ((scale & (scale - 1 )) != 0 )
throw new Error ("data type scale not a power of two" );
}
private long checkedByteOffset (int i) {
if (i < 0 || i >= array.length)
throw new 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 原子操作方法实现的。让我们简单看几个常用的方法:
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 ;
}
public final int getAndAdd (int i, int delta) {
return unsafe.getAndAddInt(array, checkedByteOffset(i), delta);
}
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 的简单使用方法:
import lombok.AllArgsConstructor;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
public class AtomicIntegerFieldUpdaterDemo {
static class Student {
private String name;
private int age;
public Student (String name, int age) {
this .name = name;
this .age = age;
}
@Override
public String toString () {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}' ;
}
}
public static class Candidate {
int id;
volatile int score;
}
static AtomicIntegerFieldUpdater<Candidate> atIntegerUpdater
= AtomicIntegerFieldUpdater.newUpdater(Candidate.class, "score" );
static AtomicReferenceFieldUpdater<Student, String> atRefUpdate
= AtomicReferenceFieldUpdater.newUpdater(Student.class, String.class, "name" );
public static AtomicInteger allScore = new AtomicInteger (0 );
public static void main (String[] args) throws InterruptedException {
final Candidate stu = new Candidate ();
Thread[] t = new Thread [10000 ];
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);
Student student = new Student ("Jerry" , 17 );
atRefUpdate.compareAndSet(student, student.name, "Jerry-1" );
System.out.println(student);
}
}
我们使用 AtomicIntegerFieldUpdater 来更新候选人(Candidate)的分数。启动 10,000 个线程进行投票。当随机值大于 0.5 时,算作一票,分数自增一次。其中,allScore 用于验证分数是否正确(实际用于验证 AtomicIntegerFieldUpdater 更新的字段是否线程安全)。当 allScore 与 score 相同时,说明投票结果正确,也代表 AtomicIntegerFieldUpdater 能正确更新字段 score 的值并且是线程安全的。
对于 AtomicReferenceFieldUpdater,我们在代码中简单演示了它的使用方法。注意在指定 AtomicReferenceFieldUpdater 的泛型时,需要两个泛型参数,一个是修改类的类型,另一个是修改字段的类型。
至于 AtomicLongFieldUpdater,它与 AtomicIntegerFieldUpdater 类似,不再赘述。接下来简单了解一下 AtomicIntegerFieldUpdater 的实现原理。它实际上是反射与 Unsafe 类的结合。AtomicIntegerFieldUpdater 是一个抽象类,实际的实现类是 AtomicIntegerFieldUpdaterImpl:
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 的源码。我提供了一些注释:
private static class AtomicIntegerFieldUpdaterImpl <T>
extends AtomicIntegerFieldUpdater <T> {
private static final Unsafe unsafe = Unsafe.getUnsafe();
private final long offset;
private final Class<T> tclass;
private final Class<?> cclass;
AtomicIntegerFieldUpdaterImpl(final Class<T> tclass,
final String fieldName,
final Class<?> caller) {
final Field field;
final int modifiers;
try {
field = AccessController.doPrivileged(
new PrivilegedExceptionAction <Field>() {
public Field run () throws NoSuchFieldException {
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) {
throw new RuntimeException (pae.getException());
} catch (Exception ex) {
throw new RuntimeException (ex);
}
Class<?> fieldt = field.getType();
if (fieldt != int .class)
throw new IllegalArgumentException ("Must be integer type" );
if (!Modifier.isVolatile(modifiers))
throw new 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 的实现:
public int incrementAndGet (T obj) {
int prev, next;
do {
prev = get(obj);
next = prev + 1 ;
} while (!compareAndSet(obj, prev, next));
return next;
}
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 机制。至此,关于 JUC 原子操作类的原理与用法介绍完毕。