第14章:原子变量与非阻塞同步
一、导读
14.1 本章学习目标
本章讲解如何使用原子变量和非阻塞同步实现无锁并发编程。无锁并发编程是高性能并发程序的关键技术,理解它对于设计高效的并发程序至关重要。
本章的核心学习目标包括:
- 掌握原子变量:AtomicInteger、AtomicLong、AtomicReference
- 理解 CAS(Compare-And-Swap):硬件级别的原子操作
- 掌握非阻塞算法:不使用锁的并发算法
- 理解 ABA 问题:CAS 的陷阱及解决方案
14.2 为什么这一章如此重要
锁是并发编程中最常用的同步机制,但锁会带来性能开销:上下文切换、内存同步等。无锁并发编程使用硬件级别的原子操作,避免了锁的开销,可以实现更高的性能。
理解无锁并发编程,对于:
- 设计高性能并发程序:避免锁的开销
- 实现无锁数据结构:无锁栈、无锁队列
- 理解 Java 并发包的实现:ConcurrentHashMap 等
二、核心概念详解
2.1 原子变量
原子变量是 Java 5 引入的一组类,它们提供了一系列原子操作。
2.1.1 AtomicInteger
// AtomicInteger 示例
AtomicInteger counter = new AtomicInteger(0);
// 原子递增
counter.incrementAndGet(); // 返回递增后的值
counter.getAndIncrement(); // 返回递增前的值
// 原子递减
counter.decrementAndGet();
counter.getAndDecrement();
// 原子加法
counter.addAndGet(10);
counter.getAndAdd(10);
// CAS 操作
counter.compareAndSet(10, 20); // 如果当前值是 10,则设置为 20
// 获取和设置
counter.get();
counter.set(100);
counter.getAndSet(200);2.1.2 AtomicLong
// AtomicLong 示例
AtomicLong timestamp = new AtomicLong(System.currentTimeMillis());
// 原子更新
timestamp.incrementAndGet();
timestamp.compareAndSet(100, 200);2.1.3 AtomicReference
// AtomicReference 示例
AtomicReference<String> ref = new AtomicReference<>("Hello");
// CAS 操作
ref.compareAndSet("Hello", "World");
// 获取和设置
String value = ref.get();
ref.set("New Value");
String oldValue = ref.getAndSet("Another Value");2.1.4 其他原子变量
- AtomicBoolean:原子布尔值
- AtomicStampedReference:带版本号的原子引用(解决 ABA 问题)
- AtomicMarkableReference:带标记的原子引用
2.2 CAS(Compare-And-Swap)
CAS 是一种硬件级别的原子操作,它比较内存位置的值,如果相等则更新为新值。
// CAS 的伪代码
boolean cas(V memoryLocation, V expectedValue, V newValue) {
if (read(memoryLocation) == expectedValue) {
write(memoryLocation, newValue);
return true;
}
return false;
}
// AtomicInteger 的 CAS 实现
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}CAS 的特点:
- 硬件级别的原子操作
- 不需要加锁
- 性能高
- 需要循环重试(自旋)
2.3 非阻塞算法
非阻塞算法使用 CAS 实现,不需要锁。
2.3.1 非阻塞计数器
// 非阻塞计数器
public class NonBlockingCounter {
private AtomicInteger value = new AtomicInteger(0);
public int increment() {
return value.incrementAndGet();
}
public int get() {
return value.get();
}
}2.3.2 非阻塞栈
// 非阻塞栈
public class ConcurrentStack<E> {
private AtomicReference<Node<E>> head = new AtomicReference<>(null);
public void push(E item) {
Node<E> newHead = new Node<>(item);
Node<E> oldHead;
do {
oldHead = head.get();
newHead.next = oldHead;
} while (!head.compareAndSet(oldHead, newHead));
}
public E pop() {
Node<E> oldHead;
Node<E> newHead;
do {
oldHead = head.get();
if (oldHead == null) return null;
newHead = oldHead.next;
} while (!head.compareAndSet(oldHead, newHead));
return oldHead.item;
}
private static class Node<E> {
final E item;
Node<E> next;
Node(E item) {
this.item = item;
}
}
}2.3.3 非阻塞队列
// 非阻塞队列(简化版)
public class ConcurrentQueue<E> {
private AtomicReference<Node<E>> head = new AtomicReference<>(null);
private AtomicReference<Node<E>> tail = new AtomicReference<>(null);
public void enqueue(E item) {
Node<E> newNode = new Node<>(item);
Node<E> oldTail;
do {
oldTail = tail.get();
if (oldTail == null) {
head.set(newNode);
} else {
oldTail.next = newNode;
}
} while (!tail.compareAndSet(oldTail, newNode));
}
public E dequeue() {
Node<E> oldHead;
Node<E> newHead;
do {
oldHead = head.get();
if (oldHead == null) return null;
newHead = oldHead.next;
} while (!head.compareAndSet(oldHead, newHead));
return oldHead.item;
}
private static class Node<E> {
final E item;
Node<E> next;
Node(E item) {
this.item = item;
}
}
}2.4 ABA 问题
ABA 问题是 CAS 的一个陷阱:值从 A 变成 B,再变回 A,CAS 会认为值没有改变。
// ABA 问题示例
AtomicReference<Integer> ref = new AtomicReference<>(1);
// 线程1:读取值为 1
Integer expected = ref.get(); // 1
// 线程2:将值改为 2,再改回 1
ref.set(2);
ref.set(1);
// 线程1:CAS 操作成功,但值实际上已经改变
ref.compareAndSet(expected, 3); // 成功,但值从 1 -> 2 -> 1 -> 32.4.1 使用 AtomicStampedReference 解决 ABA 问题
// 使用 AtomicStampedReference 解决 ABA 问题
AtomicStampedReference<Integer> ref = new AtomicStampedReference<>(1, 0);
// 线程1:读取值和版本号
int[] stampHolder = new int[1];
Integer expected = ref.get(stampHolder); // 1, 版本号 0
int expectedStamp = stampHolder[0];
// 线程2:修改值,版本号增加
ref.compareAndSet(1, 2, 0, 1); // 值 1 -> 2, 版本号 0 -> 1
ref.compareAndSet(2, 1, 1, 2); // 值 2 -> 1, 版本号 1 -> 2
// 线程1:CAS 操作失败,因为版本号不匹配
ref.compareAndSet(expected, 3, expectedStamp, 3); // 失败三、重要知识点
3.1 原子变量的实现原理
原子变量使用 CAS 实现,底层调用 Unsafe 类的 native 方法。
// AtomicInteger 的实现
public class AtomicInteger {
private volatile int value;
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset(
AtomicInteger.class.getDeclaredField("value"));
} catch (Exception e) {
throw new Error(e);
}
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
}3.2 CAS 的性能
CAS 的性能特点:
- 硬件级别的原子操作,不需要加锁
- 需要循环重试(自旋),可能消耗 CPU
- 适合竞争不激烈的场景
- 不适合竞争激烈的场景(大量自旋)
3.3 非阻塞算法的优势
非阻塞算法的优势:
- 不需要锁,避免死锁
- 避免线程阻塞和唤醒的开销
- 适合高并发场景
- 可以实现更高的吞吐量
3.4 非阻塞算法的劣势
非阻塞算法的劣势:
- 实现复杂
- 需要处理 ABA 问题
- 可能需要大量自旋
- 不适合复杂的操作
四、常见误区与难点
4.1 误区一:"原子变量总是比锁快"
原子变量在竞争不激烈时比锁快,但在竞争激烈时可能比锁慢(大量自旋)。
4.2 误区二:"CAS 不会失败"
CAS 可能失败,需要循环重试。如果竞争激烈,可能导致大量自旋。
4.3 难点:理解非阻塞算法的实现
非阻塞算法的实现比较复杂,需要仔细处理并发情况。
4.4 难点:理解 ABA 问题
ABA 问题是 CAS 的一个陷阱,需要使用 AtomicStampedReference 解决。
五、实践应用
5.1 使用原子变量实现计数器
public class Counter {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet();
}
public int getCount() {
return count.get();
}
}5.2 使用原子变量实现单例模式
public class Singleton {
private static AtomicReference<Singleton> instance = new AtomicReference<>();
private Singleton() {}
public static Singleton getInstance() {
Singleton singleton = instance.get();
if (singleton == null) {
singleton = new Singleton();
if (instance.compareAndSet(null, singleton)) {
return singleton;
}
}
return instance.get();
}
}5.3 使用原子变量实现重试机制
public class RetryMechanism {
private AtomicInteger retryCount = new AtomicInteger(0);
private static final int MAX_RETRIES = 3;
public void executeWithRetry(Runnable task) {
while (retryCount.get() < MAX_RETRIES) {
try {
task.run();
return;
} catch (Exception e) {
retryCount.incrementAndGet();
}
}
throw new RuntimeException("Max retries exceeded");
}
}六、本章小结
第14章讲解了原子变量与非阻塞同步:
原子变量:AtomicInteger、AtomicLong、AtomicReference
CAS:硬件级别的原子操作
非阻塞算法:不使用锁的并发算法
ABA 问题:CAS 的陷阱及解决方案
无锁并发编程是高性能并发程序的关键技术,理解它对于设计高效的并发程序至关重要。
后续章节将讲解 Java 内存模型。