Java 中的原子操作

写这篇博客起源于一道面试题 i++ 是不是原子操作?

那到底什么是原子操作?
所谓原子操作,就是“不可中断的一个或一系列操作”。在确认一个操作是原子的情况下,在多线程环境里,我们可以避免仅仅为保护这个操作在外围加上性能昂贵的锁,甚至借助于原子操作,我们可以实现互斥锁。

Java中有哪些原子操作呢?查询资料后我总结出了以下几个:

  1. 原始类型:原始类型(long和double的赋值操作在32位操作系统上是非原子操作)的简单读取、写入操作(i++是非原子操作)。
  2. volatile:使用volatile修饰的变量的简单读取、写入操作。
  3. 原子类:原子类(例如AtomicInteger、AtomicBoolean)的读取、写入操作,注意incrementAndGet自增操作具有原子性。
  4. 并发锁:使用synchronize或者Lock进行限定的并发锁,其中的代码都具有原子性。

注意:volatile并不保证原子性,所以即使变量有volatile修饰,对变量的“复杂”操作并不是原子操作,即保证简单读取、写入操作是原子操作。

<think>我们正在讨论Java中的原子操作及其实现原理。根据引用内容,原子操作是不可中断的一个或一系列操作。在多处理器环境中,实现原子操作需要特殊机制。Java通过`java.util.concurrent.atomic`包提供了一系列原子类,其底层实现依赖于处理器的CAS(Compare-and-Swap)指令,同时为了确保原子性,处理器提供了总线锁或缓存锁机制。 下面我将详细解答用户的问题: 1. **Java原子操作有哪些?** Java中的原子操作主要分为以下几类(参考之前分类): - 基本类型:`AtomicInteger`, `AtomicLong`, `AtomicBoolean` - 引用类型:`AtomicReference`, `AtomicStampedReference`, `AtomicMarkableReference` - 数组类型:`AtomicIntegerArray`, `AtomicLongArray`, `AtomicReferenceArray` - 字段更新器:`AtomicIntegerFieldUpdater`, `AtomicLongFieldUpdater`, `AtomicReferenceFieldUpdater` - 累加器(JDK8+):`LongAdder`, `DoubleAdder`, `LongAccumulator`, `DoubleAccumulator` 2. **实现原理** - **处理器级别**:通过总线锁或缓存锁保证原子性(引用[1][2]) - **总线锁**:使用处理器提供的一个`LOCK#`信号,当一个处理器在总线上输出此信号时,其他处理器的请求将被阻塞,该处理器独占共享内存。 - **缓存锁**:通过缓存一致性协议(如MESI)来保证原子性。在修改内存地址时,如果该地址已经被缓存在处理器内部,则不会声明`LOCK#`信号,而是修改内部的内存地址,并利用缓存一致性协议保证原子性。 - **Java级别**:通过Unsafe类调用本地方法实现CAS操作(引用[4]) ```java public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x); ``` 其中,`o`是对象,`offset`是字段偏移量,`expected`是期望值,`x`是更新值。 **CAS操作原理**(引用[1][2][4]): CAS操作包含三个操作数:内存位置(V)、预期原值(A)和新值(B)。当且仅当内存位置的值等于预期原值时,处理器才会将该位置的值更新为新值。否则,处理器不做任何操作。无论哪种情况,都会返回该位置之前的值。CAS操作原子的,由一条处理器指令完成(如x86的`CMPXCHG`指令)。 **自旋CAS**:在Java原子类中,如果CAS操作失败,会通过循环不断尝试,直到成功为止。例如`AtomicInteger`的`getAndIncrement()`方法: ```java public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); } ``` `Unsafe.getAndAddInt`的源码: ```java public final int getAndAddInt(Object o, long offset, int delta) { int v; do { v = getIntVolatile(o, offset); // 读取当前值 } while (!compareAndSwapInt(o, offset, v, v + delta)); // 循环尝试直到CAS成功 return v; } ``` **ABA问题**:CAS操作可能会遇到ABA问题,即一个值原来是A,变成了B,又变回了A。此时使用CAS检查时会发现值没有变化,但实际上已经发生了变化。解决方案是使用版本号(`AtomicStampedReference`)或标记(`AtomicMarkableReference`)。 3. **高性能累加器LongAdder**(引用[3]) 在极高并发下,`AtomicLong`的自旋CAS操作可能因为大量线程竞争同一变量而导致性能下降。`LongAdder`通过分散热点来提高性能。其核心思想是:将单个变量拆分成多个变量(称为Cell),每个线程操作自己的Cell,最后将多个Cell的值累加起来得到总和。这样减少了竞争,提高了并发性能。 `LongAdder`的结构: - `base`:基础值,当没有竞争时直接操作该值 - `Cell[] cells`:当出现竞争时,使用Cell数组分散热点 `add`方法流程: 1. 尝试使用CAS更新`base`值 2. 如果失败,则尝试在Cell数组中更新自己线程对应的Cell(通过哈希算法确定位置) 3. 如果Cell数组未初始化或Cell未创建,则进行初始化或扩容 `sum`方法(获取总和): ```java public long sum() { Cell[] cs = cells; long sum = base; if (cs != null) { for (Cell c : cs) if (c != null) sum += c.value; } return sum; } ``` 注意:`sum`方法在没有并发更新时是准确的,但在并发更新时可能不是精确值(因为没有加锁)。 4. **原子类与锁的性能对比** 在低竞争环境下,原子类(如`AtomicInteger`)的性能优于锁(如`synchronized`或`ReentrantLock`),因为避免了线程上下文切换。在高竞争环境下,`LongAdder`的性能显著优于`AtomicLong`,因为它分散了竞争。 5. **适用场景** - 计数器:低竞争用`AtomicInteger`,高竞争用`LongAdder` - 状态标志:`AtomicBoolean` - 对象引用更新:无ABA问题用`AtomicReference`,有ABA问题用`AtomicStampedReference` - 数组元素更新:`AtomicIntegerArray`等 - 对象字段更新:`AtomicIntegerFieldUpdater`(适用于已存在的对象,无需创建新的原子对象) 6. **限制** 原子类只能保证单个变量的原子性,对于多个变量的操作(复合操作)需要借助锁或循环CAS(例如使用`AtomicReference`来保证多个变量的原子更新)。 根据以上内容,整理回答如下:</think>### Java原子操作详解:实现原理与原子类机制 #### 一、原子操作的核心定义 原子操作指**不可被中断的一个或一系列操作**,必须满足: 1. **原子性**:操作要么完全执行,要么完全不执行 2. **可见性**:操作结果立即对所有线程可见 3. **有序性**:禁止指令重排序优化 #### 二、处理器级原子操作实现原理 原子操作的硬件基础由CPU提供: 1. **总线锁机制**(解决原子性问题) - 当处理器发出`LOCK#`信号时,独占内存总线[^1] - 其他处理器被阻塞,无法访问内存 - 示例:执行`i++`时锁定总线,确保读-改-写操作原子性 ```assembly LOCK CMPXCHG ; x86原子指令 ``` 2. **缓存锁机制**(性能优化) - 基于**缓存一致性协议**(如MESI) - 只锁定特定内存地址,而非整个总线 - 当检测到共享变量被修改时,使其他处理器的缓存失效[^2] #### 三、Java原子类实现原理 Java通过`Unsafe`类调用本地CAS指令: ```java // AtomicInteger核心源码 public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); } ``` - **CAS操作流程**: 1. 读取当前值$V$ 2. 计算更新值$V' = V + \Delta$ 3. 提交时检查$V$是否被修改 4. 未修改则更新,否则重试(自旋) - **内存屏障保证可见性**: ```java public final int get() { return value; // volatile读屏障 } public final void set(int newValue) { value = newValue; // volatile写屏障 } ``` #### 四、Java原子类分类及特性 | **类型** | **实现类** | **CAS指令** | **适用场景** | |----------------|-------------------------------|-----------------------|--------------------------| | 基础类型 | `AtomicInteger`, `AtomicLong` | `compareAndSwapInt` | 计数器、状态标志 | | 引用类型 | `AtomicReference` | `compareAndSwapObject`| 对象引用更新 | | 数组类型 | `AtomicIntegerArray` | 元素级CAS | 并发数组操作 | | 版本控制 | `AtomicStampedReference` | 带版本号CAS | 解决ABA问题[^4] | | 高性能累加器 | `LongAdder` | 分段CAS | 高并发统计[^3] | #### 五、关键优化技术解析 1. **分段CAS(LongAdder)** ```mermaid graph LR A[add(x)调用] --> B{尝试更新base} B -->|成功| C[返回] B -->|失败| D[初始化Cell数组] D --> E[哈希到特定Cell] E --> F[更新Cell值] ``` - **设计优势**: - 基础值$base$ + Cell数组$cells[0..n-1]$ - 总数值$sum = base + \sum_{i=0}^{n-1} cells[i]$ - 并发写分散到不同Cell,减少冲突[^3] 2. **ABA问题解决方案** - **问题本质**:值A→B→A的变化无法被检测 - **版本控制**: ```java // AtomicStampedReference实现 public boolean compareAndSet(V expected, V newValue, int expectedStamp, int newStamp) { Pair<V> current = pair; return (current.reference == expected && current.stamp == expectedStamp) && ((newValue == current.reference && newStamp == current.stamp) || casPair(current, Pair.of(newValue, newStamp))); } ``` #### 六、原子操作 vs 锁机制 | **特性** | **原子类** | **synchronized** | |----------------|-----------------|------------------| | 实现机制 | CAS自旋 | 管程(Monitor) | | 线程阻塞 | 非阻塞 | 阻塞 | | 竞争处理 | 乐观锁 | 悲观锁 | | 高并发性能 | 优(无上下文切换)| 差 | | 复合操作支持 | 需手动循环实现 | 天然支持 | > **最佳实践**: > - 低竞争场景:优先选用`AtomicInteger`等基础原子类 > - 高并发计数:使用`LongAdder`(如QPS统计) > - 对象引用更新:带版本号的`AtomicStampedReference` > - 复合操作:使用锁或`compareAndSet`循环实现 --- ### 相关问题 1. CAS操作在高并发场景下可能出现哪些性能问题?如何优化? 2. `LongAdder`的`sum()`方法为什么在并发环境下可能不准确? 3. 如何用`AtomicReference`实现无锁栈(Lock-Free Stack)? 4. 原子类和`volatile`关键字在内存可见性保证上有何异同? 5. ABA问题在实际系统中会导致哪些具体风险?请举例说明。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值