在并发编程中,确保线程安全是一个重要话题。为了避免多个线程对同一数据进行竞争访问,Java 提供了原子类(Atomic Classes)来保证并发场景下的线程安全,而原子类最核心的实现机制就是 CAS(Compare And Swap) 操作。本文将详细解析原子类如何利用 CAS 实现线程安全,并且带你走进底层实现。
一、原子类与锁的比较
原子类和锁在并发编程中有着相似的作用:它们都能保证多个线程之间访问共享资源时的线程安全。然而,原子类相比锁有明显的优势:
- 粒度更细:原子类的粒度通常是变量级别的,而锁的粒度一般较大,可能会锁住多个变量,甚至整个方法或代码块。
- 效率更高:原子类的效率通常高于锁机制,尤其是在低竞争场景下。因为原子类操作通过 CAS 完成,不需要阻塞线程,避免了上下文切换的开销。
二、Java中的六类原子类
Java 提供了多种原子类,常见的有以下几类:
-
Atomic 基本类型原子类*
AtomicInteger
、AtomicLong
、AtomicBoolean
-
Atomic*Array 数组类型原子类
AtomicIntegerArray
、AtomicLongArray
、AtomicReferenceArray
-
Atomic*Reference 引用类型原子类
AtomicReference
、AtomicStampedReference
、AtomicMarkableReference
-
Atomic*FieldUpdater 升级类型原子类
AtomicIntegerFieldUpdater
、AtomicLongFieldUpdater
、AtomicReferenceFieldUpdater
-
Adder 累加器
LongAdder
、DoubleAdder
-
Accumulator 积累器
LongAccumulator
、DoubleAccumulator
三、AtomicInteger: 最典型的基本类型原子类
我们以 AtomicInteger
为例来解释原子类的使用。AtomicInteger
是 int
类型的封装,能够在并发场景下安全地对 int
类型的变量进行原子性操作。
AtomicInteger 常用方法
- get():获取当前值。
- getAndSet(int newValue):获取当前值并设置新的值。
- getAndIncrement():获取当前值并自增。
- getAndDecrement():获取当前值并自减。
- getAndAdd(int delta):获取当前值并加上指定值。
- compareAndSet(int expect, int update):如果当前值等于预期值,则以原子方式更新为新值。
四、如何利用 CAS 实现原子操作?
CAS(Compare And Swap)是原子操作的基础,它通过比较并交换的方式保证线程安全。我们以 AtomicInteger
中的 getAndAdd(int delta)
方法为例来说明 CAS 的底层实现。
getAndAdd 方法实现
public final int getAndAdd(int delta) {return unsafe.getAndAddInt(this, valueOffset, delta);
}
该方法通过调用 Unsafe.getAndAddInt()
实现原子操作,使用 Unsafe
类来操作内存数据。Unsafe
是 Java 提供的一个类,能够直接操作内存,因此它是 CAS 操作的核心。
Unsafe 类与内存操作
Unsafe
类通过本地方法(native methods)提供了底层硬件级别的原子操作,使得 Java 可以通过 unsafe
实现高效的原子操作,而无需使用锁。我们来详细看看 AtomicInteger
如何使用 Unsafe
类来进行 CAS 操作。
public class AtomicInteger {private static final Unsafe unsafe = Unsafe.getUnsafe();private static final long valueOffset;static {try {valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));} catch (Exception ex) {throw new Error(ex);}}private volatile int value;public final int get() {return value;}// 其他方法...
}
AtomicInteger
的 value
变量是 volatile
修饰的,确保线程可见性。valueOffset
是通过 unsafe.objectFieldOffset()
方法获取的 value
字段的内存偏移量,它代表 value
在内存中的位置。
getAndAddInt 方法的实现
getAndAddInt
方法通过循环加上 CAS 来实现原子更新:
public final int getAndAddInt(Object object, long valueOffset, int delta) {int expectedValue;do {expectedValue = this.getIntVolatile(object, valueOffset);} while (!this.compareAndSwapInt(object, valueOffset, expectedValue, expectedValue + delta));return expectedValue;
}
关键步骤解释:
- expectedValue:首先获取当前
value
的值。 - compareAndSwapInt:如果当前值与预期值相等,则将
value
更新为expectedValue + delta
。 - 如果更新失败(例如其他线程修改了
value
),则重新获取最新的值,再次尝试更新,直到成功。
这个过程通过循环加 CAS 的方式确保了原子性,避免了线程间的冲突。
五、CAS的成功与失败
CAS 操作可能失败。失败的原因是多个线程竞争更新同一数据时,可能会出现“脏读”现象。比如,一个线程 A 读取了值 X
,线程 B 在此过程中修改了值 X
。当线程 A 尝试通过 CAS 更新值时,操作会失败,并重新获取值进行重试。
这种机制虽然能保证线程安全,但也会带来一些性能问题,特别是在高度竞争的情况下,CAS 可能会导致“活锁”现象,影响效率。
六、总结
通过原子类和 CAS,我们能够在并发环境下实现高效的线程安全操作。与锁机制相比,CAS 通过避免线程阻塞,通常能够提供更高的性能。理解 CAS 的底层原理,掌握如何使用原子类,是每个并发编程开发者必备的技能。
如果你对原子操作和并发编程有更多的兴趣,欢迎关注我的 CSDN 博客,我们将继续深入讨论并发编程中的高级技术与优化技巧!