Java 并发编程:原子类(Atomic Classes)核心技术的深度解析
在高并发场景下,线程安全是一个重要的话题。Atomic
类通过高效的 CAS(Compare-And-Swap)机制,为开发者提供了一种无需锁的线程安全解决方案。本篇文章将系统讲解 Java 原子类的核心概念、常用成员、使用方法以及实际应用。
一、概述
什么是原子操作?
原子操作是指操作在执行过程中不可被中断,要么全部执行成功,要么全部失败。
为什么需要 Atomic 类?
- 传统方式的局限性:
- 使用
synchronized
或ReentrantLock
确保线程安全,但代价是性能的下降。
- 使用
- Atomic 类的优势:
- 通过 CAS 实现非阻塞操作,性能更高。
二、Atomic 类的核心成员
1. Java 提供了一系列 Atomic
类,适用于基本类型、引用类型以及特定场景。
类名 | 描述 |
---|---|
AtomicInteger |
对 int 类型的原子操作 |
AtomicLong |
对 long 类型的原子操作 |
AtomicBoolean |
对布尔值的原子操作 |
AtomicReference |
对引用类型的原子操作 |
AtomicStampedReference |
带版本号的引用原子操作,解决 ABA 问题 |
AtomicMarkableReference |
带布尔标记的引用原子操作 |
2. 各类核心方法详情
1. AtomicInteger 和 AtomicLong
方法名 | 描述 | 示例代码 |
---|---|---|
get() |
获取当前值 | int value = atomicInteger.get(); |
set(int newValue) |
设置新值(非原子操作,直接替换) | atomicInteger.set(10); |
lazySet(int newValue) |
延迟设置新值,最终会在必要时更新 | atomicInteger.lazySet(10); |
compareAndSet(int expect, int update) |
CAS 操作:期望值与当前值相同时更新 | atomicInteger.compareAndSet(5, 10); |
getAndSet(int newValue) |
设置新值,并返回设置前的值 | int oldValue = atomicInteger.getAndSet(10); |
getAndIncrement() |
原子递增操作,返回递增前的值 | int oldValue = atomicInteger.getAndIncrement(); |
incrementAndGet() |
原子递增操作,返回递增后的值 | int newValue = atomicInteger.incrementAndGet(); |
getAndDecrement() |
原子递减操作,返回递减前的值 | int oldValue = atomicInteger.getAndDecrement(); |
decrementAndGet() |
原子递减操作,返回递减后的值 | int newValue = atomicInteger.decrementAndGet(); |
addAndGet(int delta) |
增加指定值,返回增加后的结果 | int newValue = atomicInteger.addAndGet(5); |
getAndAdd(int delta) |
增加指定值,返回增加前的结果 | int oldValue = atomicInteger.getAndAdd(5); |
weakCompareAndSet(int expect, int update) |
类似 compareAndSet ,但不能保证成功(可能在高竞争下失败) |
atomicInteger.weakCompareAndSet(5, 10); |
2. AtomicBoolean
方法名 | 描述 | 示例代码 |
---|---|---|
get() |
获取当前布尔值 | boolean value = atomicBoolean.get(); |
set(boolean newValue) |
设置布尔值(非原子操作,直接替换) | atomicBoolean.set(true); |
compareAndSet(boolean expect, boolean update) |
CAS 操作:期望值与当前值相同时更新 | atomicBoolean.compareAndSet(false, true); |
getAndSet(boolean newValue) |
设置新值,并返回设置前的值 | boolean oldValue = atomicBoolean.getAndSet(true); |
3. AtomicReference
方法名 | 描述 | 示例代码 |
---|---|---|
get() |
获取当前引用 | Object ref = atomicReference.get(); |
set(V newValue) |
设置新引用值 | atomicReference.set(newValue); |
lazySet(V newValue) |
延迟设置引用值,最终会在必要时更新 | atomicReference.lazySet(newValue); |
compareAndSet(V expect, V update) |
CAS 操作:期望值与当前值相同时更新 | atomicReference.compareAndSet(oldValue, newValue); |
getAndSet(V newValue) |
设置新值,并返回设置前的值 | Object oldRef = atomicReference.getAndSet(newValue); |
weakCompareAndSet(V expect, V update) |
类似 compareAndSet ,但不能保证成功(可能失败) |
atomicReference.weakCompareAndSet(oldRef, newRef); |
4. AtomicStampedReference
方法名 | 描述 | 示例代码 |
---|---|---|
getReference() |
获取当前引用值 | Object ref = atomicStampedRef.getReference(); |
getStamp() |
获取当前版本号 | int stamp = atomicStampedRef.getStamp(); |
compareAndSet(V expectReference, V newReference, int expectStamp, int newStamp) |
CAS 操作:比较并更新引用值和版本号 | atomicStampedRef.compareAndSet(oldRef, newRef, oldStamp, newStamp); |
set(V newReference, int newStamp) |
设置新值及版本号 | atomicStampedRef.set(newRef, newStamp); |
get(int[] stampHolder) |
获取当前引用值和版本号(通过数组存储版本号) | Object ref = atomicStampedRef.get(stampHolder); |
5. AtomicMarkableReference
方法名 | 描述 | 示例代码 |
---|---|---|
getReference() |
获取当前引用值 | Object ref = atomicMarkableRef.getReference(); |
isMarked() |
获取当前标记状态 | boolean isMarked = atomicMarkableRef.isMarked(); |
compareAndSet(V expectReference, V newReference, boolean expectMark, boolean newMark) |
CAS 操作:比较并更新引用值和标记状态 | atomicMarkableRef.compareAndSet(oldRef, newRef, false, true); |
set(V newReference, boolean newMark) |
设置新值及标记状态 | atomicMarkableRef.set(newRef, true); |
get(boolean[] markHolder) |
获取当前引用值和标记状态(通过数组存储标记状态) | Object ref = atomicMarkableRef.get(markHolder); |
3. 说明与应用场景
- 数值类型(
AtomicInteger
和AtomicLong
)- 常用场景:计数器、统计数据、自增 ID。
- 关键方法:
incrementAndGet()
,addAndGet()
,compareAndSet()
。
- 布尔类型(
AtomicBoolean
)- 常用场景:线程安全的标志位、控制任务执行状态。
- 关键方法:
compareAndSet()
,getAndSet()
。
- 引用类型(
AtomicReference
)- 常用场景:无锁栈、队列、链表的实现。
- 关键方法:
compareAndSet()
。
- 带版本号的引用(
AtomicStampedReference
)- 常用场景:解决 ABA 问题,如内存管理或任务调度。
- 关键方法:
compareAndSet()
。
- 带标记的引用(
AtomicMarkableReference
)- 常用场景:标记对象状态,如垃圾回收标记。
- 关键方法:
compareAndSet()
。
4. 方法分类总结
- 常用方法 :
get()
,set()
,compareAndSet()
- 扩展操作 :
getAndIncrement()
,incrementAndGet()
,getAndAdd()
- 特殊方法 :
weakCompareAndSet()
(弱版本 CAS),lazySet()
(延迟更新) - 高级方法 :
AtomicStampedReference
和AtomicMarkableReference
的多值操作
三、原子操作的底层原理
1. CAS(Compare-And-Swap)机制
CAS 是一种乐观锁机制,尝试更新值时会比较预期值和当前值是否一致。
工作流程:
- 读取当前值和预期值。
- 如果预期值与当前值一致,则更新值。
- 如果不一致,重新尝试,直至更新成功。
示例:CAS 伪代码
java
while (true) {
int current = getCurrentValue(); // 获取当前值
int newValue = current + 1; // 计算新值
if (compareAndSet(current, newValue)) { // 如果当前值未被修改,更新为新值
System.out.println("CAS 成功,更新后的值为: " + newValue);
break;
} else {
System.out.println("CAS 失败,重试...");
}
}
优势:
- 非阻塞,性能高。
缺陷:
- ABA 问题: 值被修改后又还原,但 CAS 检测不到。
- 自旋开销: 在高竞争场景下可能导致性能下降。
四、各种 Atomic 类的使用方法
1. AtomicInteger
和 AtomicLong
常用方法
get()
/set(value)
:获取或设置值。incrementAndGet()
/getAndIncrement()
:递增操作。compareAndSet(expect, update)
:CAS 更新。
示例:计数器的实现
java
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicCounter {
private AtomicInteger counter = new AtomicInteger(0);
public void increment() {
counter.incrementAndGet();
}
public int getCount() {
return counter.get();
}
public static void main(String[] args) {
AtomicCounter atomicCounter = new AtomicCounter();
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
atomicCounter.increment();
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("最终计数: " + atomicCounter.getCount());
}
}
2. AtomicBoolean
示例:线程安全的标志位
java
import java.util.concurrent.atomic.AtomicBoolean;
public class AtomicBooleanExample {
private AtomicBoolean flag = new AtomicBoolean(false);
public void toggle() {
flag.set(!flag.get());
}
public boolean getFlag() {
return flag.get();
}
public static void main(String[] args) {
AtomicBooleanExample example = new AtomicBooleanExample();
example.toggle();
System.out.println("标志位: " + example.getFlag());
}
}
3. AtomicReference
示例:线程安全的链表节点
java
import java.util.concurrent.atomic.AtomicReference;
public class AtomicNode<T> {
private AtomicReference<T> value;
public AtomicNode(T initialValue) {
value = new AtomicReference<>(initialValue);
}
public T getValue() {
return value.get();
}
public void setValue(T newValue) {
value.set(newValue);
}
}
4. AtomicStampedReference
解决 ABA 问题
java
import java.util.concurrent.atomic.AtomicStampedReference;
public class AtomicStampedExample {
public static void main(String[] args) {
AtomicStampedReference<Integer> ref = new AtomicStampedReference<>(1, 0);
int stamp = ref.getStamp();
System.out.println("初始版本: " + stamp);
ref.compareAndSet(1, 2, stamp, stamp + 1);
System.out.println("新值: " + ref.getReference() + ",新版本: " + ref.getStamp());
}
}
5. AtomicMarkableReference
带标记的引用操作
java
import java.util.concurrent.atomic.AtomicMarkableReference;
public class AtomicMarkableExample {
public static void main(String[] args) {
AtomicMarkableReference<Integer> ref = new AtomicMarkableReference<>(1, false);
boolean marked = ref.isMarked();
System.out.println("初始标记: " + marked);
ref.set(2, true);
System.out.println("新值: " + ref.getReference() + ",新标记: " + ref.isMarked());
}
}
五、实战案例
1.高并发计数器:
java
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicCounterExample {
// 线程安全的计数器
private AtomicInteger counter = new AtomicInteger(0);
// 递增操作
public void increment() {
int newValue = counter.incrementAndGet();
System.out.println(Thread.currentThread().getName() + " 递增后计数: " + newValue);
}
// 获取当前计数
public int getCount() {
return counter.get();
}
// 重置计数
public void reset() {
counter.set(0);
System.out.println("计数器已重置");
}
public static void main(String[] args) {
AtomicCounterExample example = new AtomicCounterExample();
// 创建任务:每个线程递增计数器 1000 次
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
example.increment();
}
};
// 创建并启动线程
Thread t1 = new Thread(task, "线程-1");
Thread t2 = new Thread(task, "线程-2");
t1.start();
t2.start();
// 等待线程完成
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 打印最终计数
System.out.println("最终计数: " + example.getCount());
// 重置计数器并再次验证
example.reset();
System.out.println("重置后计数: " + example.getCount());
}
}
- 日志打印 :
- 在
increment
方法中打印当前线程名和递增后的计数值,便于观察线程行为。
- 在
- 计数器重置功能 :
- 添加
reset
方法,用于将计数器重置为0
,并打印重置日志。
- 添加
- 线程命名 :
- 为线程指定名称,日志更具可读性。
运行示例
运行后输出示例(不同机器的线程调度顺序可能不同):
线程-1 递增后计数: 1
线程-2 递增后计数: 2
线程-1 递增后计数: 3
线程-2 递增后计数: 4
...
最终计数: 2000
计数器已重置
重置后计数: 0
扩展功能与测试
1. 多线程扩展
增加线程数量,模拟更高并发的场景:
java
public static void main(String[] args) {
AtomicCounterExample example = new AtomicCounterExample();
int threadCount = 10;
int incrementsPerThread = 1000;
// 创建多个线程任务
Runnable task = () -> {
for (int i = 0; i < incrementsPerThread; i++) {
example.increment();
}
};
// 创建并启动线程
Thread[] threads = new Thread[threadCount];
for (int i = 0; i < threadCount; i++) {
threads[i] = new Thread(task, "线程-" + i);
threads[i].start();
}
// 等待所有线程完成
for (Thread thread : threads) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 打印最终计数
System.out.println("最终计数: " + example.getCount());
}
输出示例:
线程-0 递增后计数: 1
线程-1 递增后计数: 2
线程-2 递增后计数: 3
...
最终计数: 10000
2. 性能测试
对比 AtomicInteger
和 synchronized
在高并发场景下的性能。
添加 Synchronized 版本计数器
java
class SynchronizedCounter {
private int counter = 0;
public synchronized void increment() {
counter++;
}
public synchronized int getCount() {
return counter;
}
}
性能测试代码
java
public static void testPerformance(String label, Runnable task, int threadCount) {
Thread[] threads = new Thread[threadCount];
long startTime = System.nanoTime();
for (int i = 0; i < threadCount; i++) {
threads[i] = new Thread(task);
threads[i].start();
}
for (Thread thread : threads) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
long endTime = System.nanoTime();
System.out.printf("%s 执行时间: %d 毫秒%n", label, (endTime - startTime) / 1_000_000);
}
public static void main(String[] args) {
int threadCount = 10;
int incrementsPerThread = 1000;
// AtomicInteger 测试
AtomicCounterExample atomicCounter = new AtomicCounterExample();
testPerformance("AtomicInteger", () -> {
for (int i = 0; i < incrementsPerThread; i++) {
atomicCounter.increment();
}
}, threadCount);
// Synchronized 测试
SynchronizedCounter syncCounter = new SynchronizedCounter();
testPerformance("Synchronized", () -> {
for (int i = 0; i < incrementsPerThread; i++) {
syncCounter.increment();
}
}, threadCount);
}
输出示例
AtomicInteger 执行时间: 12 毫秒
Synchronized 执行时间: 25 毫秒
3. 减法操作与复位
实现递减功能
在 AtomicCounterExample
中添加递减功能:
java
public void decrement() {
int newValue = counter.decrementAndGet();
System.out.println(Thread.currentThread().getName() + " 递减后计数: " + newValue);
}
调用示例
example.decrement();
System.out.println("递减后计数: " + example.getCount());
总结
通过上述代码的完善和扩展,AtomicCounterExample
已支持以下功能:
- 线程安全的计数器 :通过
AtomicInteger
确保线程安全。 - 日志记录:实时记录每次操作,便于观察线程行为。
- 重置功能:支持计数器重置,便于重复使用。
- 多线程支持:支持高并发场景下的测试。
- 性能对比 :展示了
AtomicInteger
相对于synchronized
的性能优势。 - 减法操作:提供递减和复位功能,扩展了计数器的适用范围。
2.无锁队列实现
生产者-消费者模型的无锁队列实现
java
import java.util.concurrent.atomic.AtomicReference;
// 无锁队列实现
public class LockFreeQueue<T> {
private static class Node<T> {
T value;
AtomicReference<Node<T>> next;
Node(T value) {
this.value = value;
this.next = new AtomicReference<>(null);
}
}
private AtomicReference<Node<T>> head = new AtomicReference<>(null);
private AtomicReference<Node<T>> tail = new AtomicReference<>(null);
public LockFreeQueue() {
Node<T> dummy = new Node<>(null); // 哑节点,防止队列空指针问题
head.set(dummy);
tail.set(dummy);
}
// 入队操作
public void enqueue(T value) {
Node<T> newNode = new Node<>(value);
while (true) {
Node<T> currentTail = tail.get();
Node<T> tailNext = currentTail.next.get();
if (currentTail == tail.get()) {
if (tailNext == null) {
// 只有当 tail.next 为空时才能插入
if (currentTail.next.compareAndSet(null, newNode)) {
// 插入成功后移动 tail
tail.compareAndSet(currentTail, newNode);
System.out.println("【生产】入队: " + value);
return;
}
} else {
// tail 已过时,推进到最新位置
tail.compareAndSet(currentTail, tailNext);
}
}
}
}
// 出队操作
public T dequeue() {
while (true) {
Node<T> currentHead = head.get();
Node<T> currentTail = tail.get();
Node<T> nextNode = currentHead.next.get();
if (currentHead == head.get()) {
if (currentHead == currentTail) {
// 队列空时 tail 和 head 应相等
if (nextNode == null) {
System.out.println("【消费】队列为空");
return null; // 队列为空
}
// 修正 tail 指向
tail.compareAndSet(currentTail, nextNode);
} else {
// 提取值并移动 head
T value = nextNode.value;
if (head.compareAndSet(currentHead, nextNode)) {
System.out.println("【消费】出队: " + value);
return value;
}
}
}
}
}
// 判断队列是否为空
public boolean isEmpty() {
return head.get().next.get() == null;
}
// 主函数:生产者-消费者模型
public static void main(String[] args) {
LockFreeQueue<Integer> queue = new LockFreeQueue<>();
// 生产者线程
Runnable producer = () -> {
for (int i = 0; i < 10; i++) {
queue.enqueue(i);
try {
Thread.sleep((long) (Math.random() * 100)); // 模拟生产延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
// 消费者线程
Runnable consumer = () -> {
while (true) {
Integer value = queue.dequeue();
if (value != null) {
try {
Thread.sleep((long) (Math.random() * 150)); // 模拟消费延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
try {
Thread.sleep(50); // 队列空时稍作等待,避免忙等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
// 启动生产者和消费者
Thread producerThread = new Thread(producer, "生产者");
Thread consumerThread1 = new Thread(consumer, "消费者1");
Thread consumerThread2 = new Thread(consumer, "消费者2");
producerThread.start();
consumerThread1.start();
consumerThread2.start();
try {
producerThread.join(); // 等待生产者完成
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
代码要点解析
-
无锁队列核心逻辑:
-
入队和出队操作都使用了 CAS(Compare-And-Set) 确保线程安全。
-
哑节点
的引入:
- 防止队列初始为空时操作
head
或tail
引发空指针异常。
- 防止队列初始为空时操作
-
Tail 的推进机制:
- 当队列插入失败时,通过
tail.compareAndSet
更新尾部节点,避免 tail 落后。
- 当队列插入失败时,通过
-
-
生产者-消费者模型:
- 使用生产者线程模拟数据生成,通过
enqueue
将数据加入队列。 - 使用多个消费者线程,随机休眠后从队列中取数据。
- 使用生产者线程模拟数据生成,通过
-
队列空闲时的处理:
- 在
dequeue
操作中,如果队列为空,消费者线程会稍作休眠以避免忙等待(浪费 CPU 资源)。
- 在
-
并发特性:
- 入队和出队操作完全独立,生产者与消费者无需直接交互即可实现数据传递。
- 支持多个生产者和消费者线程同时操作队列。
运行输出示例
程序运行输出可能类似于以下内容(顺序因线程调度不同而异):
【生产】入队: 0
【生产】入队: 1
【消费】出队: 0
【生产】入队: 2
【消费】出队: 1
【消费】出队: 2
【消费】队列为空
【生产】入队: 3
【消费】出队: 3
【生产】入队: 4
【生产】入队: 5
【消费】出队: 4
【消费】出队: 5
3.解决 ABA 问题:
什么是 ABA 问题?
- ABA 问题描述的是一个变量从
A
变为了B
,然后又变回了A
,但这种变化无法被普通的 CAS 操作检测到。 - 对于普通的
AtomicReference
,只比较值是否一致,无法感知值是否经历了中间变化。
AtomicStampedReference
的作用
- 通过引入"版本号"(或时间戳),可以追踪变量的历史变化。
- 每次 CAS 操作时,不仅比较当前值,还比较版本号。
代码功能
- 初始值为
100
,版本号为0
。 - 模拟 ABA 问题:值从
100 -> 200 -> 100
,版本号从0 -> 1 -> 2
。 - 尝试更新时,通过版本号检测到值虽一致,但已被修改过,从而防止 ABA 问题。
代码实现
java
import java.util.concurrent.atomic.AtomicStampedReference;
public class ABAProblemSolver {
public static void main(String[] args) {
// 初始化 AtomicStampedReference
AtomicStampedReference<Integer> atomicRef = new AtomicStampedReference<>(100, 0);
// 获取初始值和版本号
int initialStamp = atomicRef.getStamp();
int initialValue = atomicRef.getReference();
System.out.println("初始值: " + initialValue + ", 初始版本: " + initialStamp);
// 模拟 ABA 问题
atomicRef.compareAndSet(initialValue, 200, initialStamp, initialStamp + 1); // 100 -> 200
atomicRef.compareAndSet(200, 100, initialStamp + 1, initialStamp + 2); // 200 -> 100
// 打印中间状态
System.out.println("值: " + atomicRef.getReference() + ", 当前版本: " + atomicRef.getStamp());
// 尝试用当前值更新版本号
boolean success = atomicRef.compareAndSet(100, 300, atomicRef.getStamp(), atomicRef.getStamp() + 1);
System.out.println("更新成功: " + success + ", 新值: " + atomicRef.getReference() + ", 新版本: " + atomicRef.getStamp());
}
}
运行过程与输出
运行逻辑
- 初始状态 :
- 值为
100
,版本号为0
。
- 值为
- 模拟 ABA 问题 :
- 第一次更新:值从
100
更新为200
,版本号从0
变为1
。 - 第二次更新:值从
200
更新回100
,版本号从1
变为2
。
- 第一次更新:值从
- 更新检测 :
- CAS 操作尝试将
100
更新为300
。 - 因版本号已从初始版本
0
变为2
,更新失败,避免 ABA 问题。
- CAS 操作尝试将
输出示例
初始值: 100, 初始版本: 0
值: 100, 当前版本: 2
更新成功: false, 新值: 100, 新版本: 2
解决 ABA 问题的关键点
- 版本号检测 :
- 每次更新值时,同时更新版本号。
- CAS 操作时,需验证版本号是否一致,确保值未被其他线程修改。
- 线程安全性 :
- 使用
AtomicStampedReference
确保操作的原子性。 - 避免了值在中间状态被其他线程干扰的问题。
- 使用
应用场景
- 内存管理: 比如垃圾回收中的引用计数。
- 任务调度: 在线程池中跟踪任务状态的变化。
- 多线程协作: 检测任务是否已被其他线程抢占执行。
4.使用 AtomicBoolean
实现线程安全的单例模式:
java
import java.util.concurrent.atomic.AtomicBoolean;
public class Singleton {
private static Singleton instance;
private static AtomicBoolean isInitialized = new AtomicBoolean(false);
private Singleton() {
// 私有构造函数
}
public static Singleton getInstance() {
if (!isInitialized.get()) {
synchronized (Singleton.class) {
if (!isInitialized.get()) {
instance = new Singleton();
isInitialized.set(true);
}
}
}
return instance;
}
public void showMessage() {
System.out.println("单例模式实例方法被调用");
}
public static void main(String[] args) {
Runnable task = () -> {
Singleton singleton = Singleton.getInstance();
singleton.showMessage();
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
}
}
6. 与其他并发工具的对比
1. 与 ReentrantLock
的对比
特性 | Atomic 类 |
ReentrantLock |
---|---|---|
性能 | 非阻塞操作,适合低竞争场景,性能高 | 阻塞操作,性能较低,但适合高竞争场景 |
复杂操作支持 | 仅支持单一变量的原子操作 | 支持复杂同步逻辑(如条件变量) |
实现方式 | 基于 CAS,自旋重试 | 基于内核阻塞 |
使用场景 | 轻量级、高频操作,简单逻辑 | 高竞争场景,复杂逻辑的同步 |
示例场景:
Atomic
类适合用在高频调用的计数器等场景,例如访问计数、并发标志位操作。ReentrantLock
适合复杂的多条件等待场景,如实现生产者-消费者模型。
2. 与 synchronized
的对比
特性 | Atomic 类 |
synchronized |
---|---|---|
性能 | 非阻塞操作,适合低竞争场景,性能高 | 阻塞操作,性能较低 |
使用难度 | 较复杂,需了解 CAS 原理 | 使用简单,语言级支持 |
线程安全范围 | 单一变量的线程安全操作 | 可保护整个方法或代码块的线程安全 |
适用场景选择:
- 如果需要简单的线程安全操作,例如单个计数器的自增,可以使用
Atomic
类。 - 如果需要保护多段代码的逻辑一致性或多个变量的同步,
synchronized
更加适合。
7. 注意事项
1. 使用场景的误区
- 多个变量操作的问题:
Atomic
类不适合需要同时操作多个变量的场景。例如,在进行两个账户之间的转账时,不能只依靠Atomic
类。 解决方案: 使用ReentrantLock
或StampedLock
提供复杂同步支持。 - 高竞争场景的性能问题: 在高竞争场景下,自旋操作可能导致 CPU 资源被过度消耗。 优化建议:
- 限制自旋时间,必要时降级为阻塞锁。
- 在可控场景下减少资源争用(如分段锁)。
2. 实现的最佳实践
- 混合使用并发工具: 在复杂场景下,可以将
Atomic
类与锁机制结合使用。例如,使用Atomic
类维护简单计数,锁机制控制复杂更新。 - 谨慎选择适用范围: 确保
Atomic
类的使用只限于低竞争、高频操作的场景,例如计数器和状态标志。
9. 扩展知识
1. JDK 中 Atomic 类的源码分析
- 底层实现:
Atomic
类通过Unsafe
类的compareAndSwapInt
或compareAndSwapLong
方法实现。- 使用 CPU 的原子指令(如
cmpxchg
)实现 CAS。
- 内存屏障:
- CAS 操作会触发内存屏障,确保变量的读写具有可见性。
volatile
关键字在Atomic
类中被广泛使用,以保证变量的可见性。
2. Unsafe
类的作用
Unsafe
类是 JDK 提供的一个底层工具类,直接操作内存。- 常见方法:
compareAndSwapInt
:执行 CAS 操作。getObjectVolatile
:获取变量的最新值,绕过缓存。
- 注意:
Unsafe
是一个非公开 API,不建议在业务代码中直接使用。
3. Java 8 的改进:LongAdder
和 LongAccumulator
LongAdder
:- 适合高并发场景下的计数操作。
- 通过分段存储计数器,降低锁竞争。
LongAccumulator
:- 支持自定义的二元操作(如求和、最大值)。
- 适合复杂的并发计算场景。
示例:LongAdder
的使用
java
import java.util.concurrent.atomic.LongAdder;
public class LongAdderExample {
public static void main(String[] args) {
LongAdder adder = new LongAdder();
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
adder.increment();
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("最终计数: " + adder.sum());
}
}
通过对 Atomic 类及其实现原理、使用场景的深入理解,开发者可以在不同场景中灵活选择适合的并发工具,从而实现性能与安全的平衡。如果有其他需求,欢迎继续探讨!