Java Atomic原子类总结
前言
在Java并发编程中,保证线程安全是一个核心问题。传统的锁机制(如synchronized和ReentrantLock)虽然能够解决线程安全问题,但存在性能开销大、可能导致线程阻塞等缺点。为了提供更高效的线程安全解决方案,Java并发包(java.util.concurrent)中提供了一系列的原子操作类,这些类基于CAS(Compare-And-Swap)机制实现,能够在不使用锁的情况下保证操作的原子性。本文将全面总结Java中的Atomic原子类家族。
1. Atomic原子类概述
1.1 什么是Atomic原子类
Atomic原子类是Java并发包中提供的一组线程安全的操作类,它们能够在多线程环境下保证对变量操作的原子性,而无需使用传统的锁机制。Atomic原子类的核心特性是原子性、可见性和有序性,这些特性使得它们在高并发场景下比传统锁机制具有更好的性能。
1.2 Atomic原子类的实现原理
Atomic原子类的实现基于CAS(Compare-And-Swap)机制,这是一种无锁算法。CAS操作包含三个操作数:
- 内存位置V
- 预期值A
- 新值B
当且仅当内存位置V的值等于预期值A时,才会将该内存位置的值更新为新值B,否则不做任何操作。整个比较并交换的操作是原子的,由CPU硬件指令保证。关于CAS机制的详细解释,可参考《CAS机制详解》。
在Java中,Atomic原子类内部通过调用sun.misc.Unsafe类提供的CAS相关方法实现原子操作。Unsafe类提供了直接操作内存的能力,并通过JNI调用底层的CPU指令来实现CAS操作。
2. Atomic原子类的分类
Java中的Atomic原子类可以分为四大类:
- 基本类型原子类:用于操作基本数据类型的原子类
- 数组类型原子类:用于操作数组中元素的原子类
- 引用类型原子类:用于操作引用类型的原子类
- 字段更新器:用于原子地更新对象的字段
下面我们将逐一介绍这些类型的原子类。
3. 基本类型原子类
基本类型原子类主要包括以下几个:
AtomicBoolean:原子操作布尔类型AtomicInteger:原子操作整型AtomicLong:原子操作长整型
这些类提供的方法类似,下面以AtomicInteger为例介绍其主要方法和使用方式。
3.1 AtomicInteger的主要方法
// 创建AtomicInteger实例,初始值为0
AtomicInteger ai = new AtomicInteger();
// 创建AtomicInteger实例,指定初始值
AtomicInteger ai = new AtomicInteger(10);
// 获取当前值
int currentValue = ai.get();
// 设置新值
ai.set(20);
// 原子地设置新值并返回旧值
int oldValue = ai.getAndSet(30);
// 原子地将当前值增加1并返回旧值
int oldValue = ai.getAndIncrement();
// 原子地将当前值增加1并返回新值
int newValue = ai.incrementAndGet();
// 原子地将当前值减少1并返回旧值
int oldValue = ai.getAndDecrement();
// 原子地将当前值减少1并返回新值
int newValue = ai.decrementAndGet();
// 原子地将给定的值加到当前值上并返回旧值
int oldValue = ai.getAndAdd(5);
// 原子地将给定的值加到当前值上并返回新值
int newValue = ai.addAndGet(5);
// 原子地更新值,使用给定的更新函数
int updatedValue = ai.updateAndGet(x -> x * 2);
// 原子地更新值,使用给定的更新函数并返回旧值
int oldValue = ai.getAndUpdate(x -> x * 2);
// 原子地将当前值与输入值进行计算并更新,使用给定的累加器函数
int updatedValue = ai.accumulateAndGet(10, (x, y) -> x + y);
// 原子地将当前值与输入值进行计算并更新,使用给定的累加器函数并返回旧值
int oldValue = ai.getAndAccumulate(10, (x, y) -> x + y);3.2 使用示例
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerExample {
private static final AtomicInteger counter = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
// 创建10个线程,每个线程对计数器增加1000次
Thread[] threads = new Thread[10];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
counter.incrementAndGet();
}
});
threads[i].start();
}
// 等待所有线程执行完成
for (Thread thread : threads) {
thread.join();
}
// 输出最终结果
System.out.println("Final counter value: " + counter.get()); // 应该输出10000
}
}4. 数组类型原子类
数组类型原子类主要包括以下几个:
AtomicIntegerArray:原子操作整型数组AtomicLongArray:原子操作长整型数组AtomicReferenceArray:原子操作引用类型数组
这些类提供的方法类似,下面以AtomicIntegerArray为例介绍其主要方法和使用方式。
4.1 AtomicIntegerArray的主要方法
// 创建指定长度的AtomicIntegerArray实例,所有元素初始值为0
AtomicIntegerArray array = new AtomicIntegerArray(10);
// 创建AtomicIntegerArray实例,使用给定的int数组初始化
int[] intArray = {1, 2, 3, 4, 5};
AtomicIntegerArray array = new AtomicIntegerArray(intArray);
// 获取指定索引位置的元素值
int value = array.get(0);
// 设置指定索引位置的元素值
array.set(0, 10);
// 原子地设置指定索引位置的元素值并返回旧值
int oldValue = array.getAndSet(0, 20);
// 原子地将指定索引位置的元素值增加1并返回旧值
int oldValue = array.getAndIncrement(0);
// 原子地将指定索引位置的元素值增加1并返回新值
int newValue = array.incrementAndGet(0);
// 原子地将指定索引位置的元素值减少1并返回旧值
int oldValue = array.getAndDecrement(0);
// 原子地将指定索引位置的元素值减少1并返回新值
int newValue = array.decrementAndGet(0);
// 原子地将给定的值加到指定索引位置的元素值上并返回旧值
int oldValue = array.getAndAdd(0, 5);
// 原子地将给定的值加到指定索引位置的元素值上并返回新值
int newValue = array.addAndGet(0, 5);
// 原子地更新指定索引位置的元素值,使用给定的更新函数
int updatedValue = array.updateAndGet(0, x -> x * 2);
// 原子地更新指定索引位置的元素值,使用给定的更新函数并返回旧值
int oldValue = array.getAndUpdate(0, x -> x * 2);
// 原子地将当前值与输入值进行计算并更新,使用给定的累加器函数
int updatedValue = array.accumulateAndGet(0, 10, (x, y) -> x + y);
// 原子地将当前值与输入值进行计算并更新,使用给定的累加器函数并返回旧值
int oldValue = array.getAndAccumulate(0, 10, (x, y) -> x + y);4.2 使用示例
import java.util.concurrent.atomic.AtomicIntegerArray;
public class AtomicIntegerArrayExample {
private static final AtomicIntegerArray array = new AtomicIntegerArray(5);
public static void main(String[] args) throws InterruptedException {
// 创建5个线程,每个线程操作数组中的一个元素
Thread[] threads = new Thread[5];
for (int i = 0; i < threads.length; i++) {
final int index = i;
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
array.incrementAndGet(index);
}
});
threads[i].start();
}
// 等待所有线程执行完成
for (Thread thread : threads) {
thread.join();
}
// 输出数组的最终状态
System.out.print("Final array values: ");
for (int i = 0; i < array.length(); i++) {
System.out.print(array.get(i) + " "); // 每个元素都应该是1000
}
}
}5. 引用类型原子类
引用类型原子类主要包括以下几个:
AtomicReference:原子操作引用类型AtomicStampedReference:带版本戳的原子引用类型,解决ABA问题AtomicMarkableReference:带标记位的原子引用类型
5.1 AtomicReference
AtomicReference用于原子地更新引用类型变量。它的使用方式与基本类型原子类类似:
// 创建AtomicReference实例,指定初始引用
AtomicReference<User> atomicUser = new AtomicReference<>(new User("Alice", 25));
// 获取当前引用
User currentUser = atomicUser.get();
// 设置新引用
atomicUser.set(new User("Bob", 30));
// 原子地设置新引用并返回旧引用
User oldUser = atomicUser.getAndSet(new User("Charlie", 35));
// 原子地比较并设置引用
boolean updated = atomicUser.compareAndSet(
new User("Charlie", 35),
new User("David", 40)
);
// 原子地更新引用,使用给定的更新函数
User updatedUser = atomicUser.updateAndGet(user -> new User(user.getName(), user.getAge() + 1));
// 原子地更新引用,使用给定的更新函数并返回旧引用
User oldUser = atomicUser.getAndUpdate(user -> new User(user.getName(), user.getAge() + 1));
// 原子地将当前引用与输入引用进行计算并更新,使用给定的累加器函数
User updatedUser = atomicUser.accumulateAndGet(
new User("Eve", 1),
(u1, u2) -> new User(u1.getName(), u1.getAge() + u2.getAge())
);
// 原子地将当前引用与输入引用进行计算并更新,使用给定的累加器函数并返回旧引用
User oldUser = atomicUser.getAndAccumulate(
new User("Frank", 1),
(u1, u2) -> new User(u1.getName(), u1.getAge() + u2.getAge())
);
// 自定义User类
class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return age == user.age && Objects.equals(name, user.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}5.2 AtomicStampedReference
AtomicStampedReference通过引入版本戳解决了ABA问题。ABA问题是指在CAS操作中,一个值从A变成B,又从B变回A,而CAS操作无法检测到这个变化。
// 创建AtomicStampedReference实例,指定初始引用和初始版本戳
AtomicStampedReference<String> atomicStampedRef = new AtomicStampedReference<>("initial", 0);
// 获取当前引用和版本戳
int[] stampHolder = new int[1];
String currentRef = atomicStampedRef.get(stampHolder);
int currentStamp = stampHolder[0];
// 原子地比较并设置引用和版本戳
boolean updated = atomicStampedRef.compareAndSet(
"initial",
"updated",
currentStamp,
currentStamp + 1
);
// 设置新引用和版本戳(不进行比较)
atomicStampedRef.set("newValue", 10);
// 尝试原子地更新引用和版本戳
boolean updated = atomicStampedRef.attemptStamp("newValue", 11);5.3 AtomicMarkableReference
AtomicMarkableReference与AtomicStampedReference类似,但它不是使用整数作为版本戳,而是使用布尔值作为标记位:
// 创建AtomicMarkableReference实例,指定初始引用和初始标记
AtomicMarkableReference<String> atomicMarkableRef = new AtomicMarkableReference<>("initial", false);
// 获取当前引用和标记
boolean[] markHolder = new boolean[1];
String currentRef = atomicMarkableRef.get(markHolder);
boolean currentMark = markHolder[0];
// 原子地比较并设置引用和标记
boolean updated = atomicMarkableRef.compareAndSet(
"initial",
"updated",
currentMark,
!currentMark
);
// 设置新引用和标记(不进行比较)
atomicMarkableRef.set("newValue", true);6. 字段更新器
字段更新器允许我们在不修改原有类的情况下,对其字段进行原子操作。主要包括以下几个:
AtomicIntegerFieldUpdater:原子更新整型字段AtomicLongFieldUpdater:原子更新长整型字段AtomicReferenceFieldUpdater:原子更新引用类型字段
6.1 AtomicIntegerFieldUpdater
使用AtomicIntegerFieldUpdater需要注意以下几点:
- 被更新的字段必须是
volatile类型的,以确保可见性 - 字段的访问权限必须是
public、protected或默认的(包可见性)
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
public class AtomicIntegerFieldUpdaterExample {
public static void main(String[] args) {
// 创建Person实例
Person person = new Person("Alice", 25);
// 创建字段更新器
AtomicIntegerFieldUpdater<Person> ageUpdater = AtomicIntegerFieldUpdater.newUpdater(Person.class, "age");
// 原子地增加年龄
ageUpdater.incrementAndGet(person);
System.out.println("Updated age: " + person.getAge()); // 输出26
// 原子地比较并设置年龄
boolean updated = ageUpdater.compareAndSet(person, 26, 30);
System.out.println("Update successful: " + updated); // 输出true
System.out.println("New age: " + person.getAge()); // 输出30
}
static class Person {
private String name;
volatile int age; // 必须是volatile类型
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
}7. Atomic原子类的优缺点
7.1 优点
- 高性能:相比传统的锁机制,Atomic原子类基于CAS实现,避免了线程切换和阻塞的开销,具有更高的性能
- 非阻塞:线程在竞争失败时不会被挂起,而是可以立即尝试再次操作
- 简单易用:API设计简洁明了,使用方便
- 无需手动释放锁:不存在死锁风险
7.2 缺点
- ABA问题:基本的Atomic原子类存在ABA问题(可以使用
AtomicStampedReference或AtomicMarkableReference解决) - 自旋开销:在高并发场景下,大量线程同时竞争同一个原子变量,会导致CAS操作失败率增加,线程不断自旋尝试,增加CPU开销
- 功能有限:Atomic原子类只提供了简单的原子操作,对于复杂的复合操作,需要额外的处理
8. Atomic原子类的使用场景
Atomic原子类适用于以下场景:
- 计数器:如统计网站访问量、接口调用次数等
- 并发标记:如标记某个资源是否被占用
- 线程安全的单例模式:可以使用
AtomicReference实现双重检查锁定的单例模式 - 无锁算法实现:如无锁队列、无锁栈等
- 并发环境下的状态管理:如状态机的实现
9. 总结
本文全面总结了Java中的Atomic原子类家族,包括基本类型原子类、数组类型原子类、引用类型原子类和字段更新器。这些原子类基于CAS机制实现,能够在不使用锁的情况下保证操作的原子性,在高并发场景下具有更好的性能。
Atomic原子类是Java并发编程中的重要工具,合理使用它们可以帮助我们编写高性能、线程安全的并发程序。但同时也需要注意它们的局限性,如ABA问题和自旋开销等,在实际应用中根据具体场景选择合适的并发控制机制。

