Atomic包装类实现原理:银行金库多保险箱系统

40 阅读5分钟

将通过一个"银行金库多保险箱系统"的故事,生动解释Atomic类的实现原理。想象一个现代化银行的金库,里面有多个保险箱,每个保险箱都有独特的保护机制,确保多个金库管理员同时操作时不会出错。

故事设定:银行金库系统

  • 🏦 ​​银行金库​​:内存中的共享数据
  • 🔒 ​​保险箱​​:AtomicInteger等原子类
  • 👨‍💼 ​​金库管理员​​:操作数据的线程
  • 📝 ​​操作指令​​:CAS(Compare And Swap)操作
  • 🔄 ​​重试机制​​:自旋操作
  • 🔬 ​​显微镜​​:Unsafe类直接操作内存

普通变量的危险操作

java
Copy
public class UnsafeBank {
    private int balance = 0; // 普通变量
    
    public void deposit(int amount) {
        // 三个步骤:读取→修改→写入
        balance = balance + amount;
    }
    
    public static void main(String[] args) throws InterruptedException {
        UnsafeBank bank = new UnsafeBank();
        
        // 10个管理员同时存款
        List<Thread> admins = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            admins.add(new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    bank.deposit(1);
                }
            }));
        }
        
        admins.forEach(Thread::start);
        for (Thread admin : admins) {
            admin.join();
        }
        
        System.out.println("最终余额: " + bank.balance);
        // 预期是10000,但实际可能小于10000
    }
}

​问题分析​​:
管理员A读取余额为100,管理员B也读取到100
管理员A计算100+1=101并写入
管理员B计算100+1=101并写入
实际只增加1元,丢失1次存款!

Atomic保险箱解决方案

java
Copy
import java.util.concurrent.atomic.AtomicInteger;

public class SafeBank {
    // 原子保险箱
    private AtomicInteger balance = new AtomicInteger(0);
    
    public void deposit(int amount) {
        // 使用原子操作
        balance.addAndGet(amount);
    }
    
    public static void main(String[] args) throws InterruptedException {
        SafeBank bank = new SafeBank();
        
        // 10个管理员同时存款
        List<Thread> admins = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            admins.add(new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    bank.deposit(1);
                }
            }));
        }
        
        admins.forEach(Thread::start);
        for (Thread admin : admins) {
            admin.join();
        }
        
        System.out.println("最终余额: " + bank.balance.get());
        // 始终是10000
    }
}

核心原理:CAS操作(比较并交换)

CAS操作流程(保险箱操作员的工作流程)

  1. ​读取当前值​​:查看保险箱当前余额(V = current)

  2. ​计算新值​​:计算存款后的余额(newValue = V + amount)

  3. ​尝试更新​​:

    • 再次查看保险箱当前值(currentNow)

    • 如果 currentNow == V(期间无人动过)

      • 则更新为 newValue
      • 返回成功
    • 否则:重新开始步骤1(自旋)

Java源码实现(AtomicInteger.addAndGet)

java
Copy
public class AtomicInteger extends Number {
    private volatile int value; // 关键:volatile保证可见性
    
    public final int addAndGet(int delta) {
        // 自旋操作(可能多次尝试)
        return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
    }
}

Unsafe类的实现:

java
Copy
public final int getAndAddInt(Object o, long offset, int delta) {
    int current;
    do {
        // 步骤1:读取当前值
        current = this.getIntVolatile(o, offset);
        // 步骤2:尝试CAS更新(当前值=current,新值=current+delta)
        // 步骤3:如果失败则自旋重试
    } while (!this.compareAndSwapInt(o, offset, current, current + delta));
    return current;
}

CAS底层:CPU指令支持

CAS操作最终映射到一条CPU指令:

  • x86架构:CMPXCHG指令
  • ARM架构:LDREX/STREX指令对
assembly
Copy
; x86汇编示例
mov eax, oldValue  ; 加载期望值
mov ebx, newValue  ; 加载新值
lock cmpxchg [mem], ebx ; 原子比较交换

Atomic类家族

1. 基本类型

  • AtomicBoolean:布尔值原子类
  • AtomicInteger:整型原子类
  • AtomicLong:长整型原子类

2. 引用类型

  • AtomicReference:对象引用原子类
  • AtomicStampedReference:带版本号的对象引用(解决ABA问题)
  • AtomicMarkableReference:带标记位的对象引用

3. 数组类型

  • AtomicIntegerArray:整型数组原子类
  • AtomicLongArray:长整型数组原子类
  • AtomicReferenceArray:引用数组原子类

4. 字段更新器

  • AtomicIntegerFieldUpdater:整型字段原子更新器
  • AtomicLongFieldUpdater:长整型字段原子更新器
  • AtomicReferenceFieldUpdater:引用字段原子更新器

解决ABA问题:带版本号的保险箱

ABA问题示例:

  1. 管理员A看到保险箱余额是100

  2. 管理员B取走100(余额=0)

  3. 管理员C存入100(余额=100)

  4. 管理员A的CAS操作:预期100→200,成功!

    • 但实际上中间发生了B和C的操作

解决方案:AtomicStampedReference

java
Copy
public class ABASolution {
    static AtomicStampedReference<Integer> balance = 
        new AtomicStampedReference<>(100, 0); // 初始值100,版本0
    
    public static void main(String[] args) {
        // 管理员A尝试修改(记录初始版本)
        int oldStamp = balance.getStamp();
        Integer oldValue = balance.getReference();
        
        // 模拟管理员B取款100
        balance.compareAndSet(100, 0, 0, 1);
        
        // 模拟管理员C存款100
        balance.compareAndSet(0, 100, 1, 2);
        
        // 管理员A尝试存款100
        boolean success = balance.compareAndSet(
            oldValue, 
            oldValue + 100, 
            oldStamp,  // 预期版本0
            oldStamp + 1);
        
        System.out.println("管理员A操作成功: " + success); // false
    }
}

性能优势:无锁算法

与锁的性能对比

操作类型锁(synchronized)Atomic CAS
低竞争100 ns20 ns
中等竞争500 ns30 ns
高竞争1000+ ns100+ ns
内存开销对象头+监视器单个变量

适用场景

  • ​推荐CAS​​:

    • 计数器、状态标志
    • 集合中的并发更新
    • 非阻塞算法
  • ​推荐锁​​:

    • 复杂数据结构的保护
    • 需要等待条件满足的操作
    • 事务性操作

高级应用:自定义原子操作

使用AtomicIntegerFieldUpdater示例:

java
Copy
public class Account {
    private volatile int balance; // 必须是volatile
    
    private static final AtomicIntegerFieldUpdater<Account> updater =
        AtomicIntegerFieldUpdater.newUpdater(Account.class, "balance");
    
    public void deposit(int amount) {
        updater.addAndGet(this, amount);
    }
}

实现原理总结

  1. ​volatile基础​​:

    • 所有Atomic类内部都使用volatile变量保证可见性
    • 写操作后强制刷新到主内存
    • 读操作前强制从主内存读取
  2. ​CAS核心​​:

    • 自旋重试机制
    • 底层CPU指令支持
    • 比较当前值与预期值
    • 相同则更新,不同则重试
  3. ​解决ABA问题​​:

    • 使用版本号或标记位
    • AtomicStampedReference
    • AtomicMarkableReference
  4. ​无锁算法优势​​:

    • 避免线程阻塞
    • 减少上下文切换
    • 提高吞吐量

正如我们的银行金库系统,Atomic类就像每个保险箱都配备了独立的操作员,通过高效的协调机制(CAS),让多个管理员能安全、高效地同时工作,而无需排队等待大金库门锁(synchronized)!

Atomic类是构建高性能并发工具的基础,在并发包中的ConcurrentHashMapLongAdder等类中都广泛使用。理解其原理是掌握Java高并发的关键一步。