14Java 并发编程实战

原子变量与非阻塞同步

无锁并发编程

原子变量CAS非阻塞算法ABA 问题

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第14章:原子变量与非阻塞同步

一、导读

14.1 本章学习目标

本章讲解如何使用原子变量和非阻塞同步实现无锁并发编程。无锁并发编程是高性能并发程序的关键技术,理解它对于设计高效的并发程序至关重要。

本章的核心学习目标包括:

  • 掌握原子变量:AtomicInteger、AtomicLong、AtomicReference
  • 理解 CAS(Compare-And-Swap):硬件级别的原子操作
  • 掌握非阻塞算法:不使用锁的并发算法
  • 理解 ABA 问题:CAS 的陷阱及解决方案

14.2 为什么这一章如此重要

锁是并发编程中最常用的同步机制,但锁会带来性能开销:上下文切换、内存同步等。无锁并发编程使用硬件级别的原子操作,避免了锁的开销,可以实现更高的性能。

理解无锁并发编程,对于:

  • 设计高性能并发程序:避免锁的开销
  • 实现无锁数据结构:无锁栈、无锁队列
  • 理解 Java 并发包的实现:ConcurrentHashMap 等

二、核心概念详解

2.1 原子变量

原子变量是 Java 5 引入的一组类,它们提供了一系列原子操作。

2.1.1 AtomicInteger

java
// 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

java
// AtomicLong 示例
AtomicLong timestamp = new AtomicLong(System.currentTimeMillis());

// 原子更新
timestamp.incrementAndGet();
timestamp.compareAndSet(100, 200);

2.1.3 AtomicReference

java
// 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 是一种硬件级别的原子操作,它比较内存位置的值,如果相等则更新为新值。

java
// 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 非阻塞计数器

java
// 非阻塞计数器
public class NonBlockingCounter {
    private AtomicInteger value = new AtomicInteger(0);
    
    public int increment() {
        return value.incrementAndGet();
    }
    
    public int get() {
        return value.get();
    }
}

2.3.2 非阻塞栈

java
// 非阻塞栈
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 非阻塞队列

java
// 非阻塞队列(简化版)
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 会认为值没有改变。

java
// 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 -> 3

2.4.1 使用 AtomicStampedReference 解决 ABA 问题

java
// 使用 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 方法。

java
// 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 使用原子变量实现计数器

java
public class Counter {
    private AtomicInteger count = new AtomicInteger(0);
    
    public void increment() {
        count.incrementAndGet();
    }
    
    public int getCount() {
        return count.get();
    }
}

5.2 使用原子变量实现单例模式

java
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 使用原子变量实现重试机制

java
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 内存模型。


14/15原子变量
0%