本文共 4648 字,大约阅读时间需要 15 分钟。
所谓的原子操作,是指在多线程环境下,一个操作如果成功执行,那么它的全部效果必须得以实现,而且在这个过程中对系统状态的修改是不可被其他线程观察或干扰的,也就是说,它是一个原子性操作。这种特性对保障线程安全至关重要。
Compare And Swap(比较并交换)是一种常见的原子操作机制,特别是在处理多线程问题时非常有用。CAS在很多现代计算架构上得到广泛支持,因为它能够在不使用锁的情况下提供基本的线程安全保证。
CAS操作的核心逻辑是这样的:
这种机制不需要加锁,能够提供接近单线程性能的响应速度。
在Java中,AtomicInteger
等原子操作类提供了多种基本原子操作:
get()
方法: 返回当前变量的值。increment()
, decrement()
方法: 递增或递减操作,能够原子性地完成。compareAndSet(int, int)
方法: 比较当前值是否等于给定值,如果等于则替换为新值,否则做 nothing。这些方法都建立在底层的至低粒度的原子操作机制上,确保了线程安全。
为了比较原子操作和传统的加锁机制在性能上的表现,我们可以设计一个简单的累加场景:
public class AtomicIntegerDemo { private static final AtomicInteger atomicInteger = new AtomicInteger(1); public static void main(String[] args) throws InterruptedException { // 测试原子操作类的性能 testAtomic(); // 使用加锁实现累加 AddWithLock syncAdd = new AddWithLock(); syncAdd.add(1000000000); System.out.println("使用加锁总时间: " + syncAdd.getTotalTime()); } private static void testAtomic() { long startTime = System.currentTimeMillis(); for (int i = 0; i < 1000000000; i++) { atomicInteger.incrementAndGet(); } long endTime = System.currentTimeMillis(); System.out.println("原子操作类累加花费的时间: " + (endTime - startTime) + " 次数: " + atomicInteger.get()); }}// 加锁实现class AddWithLock implements Runnable { private final Object lock = new Object(); private volatile int counter = 1; private long totalTime = 0; public void add(final long count) { synchronized (lock) { for (long i = 0; i < count; i++) { counter++; totalTime += 1L; } } } public long getTotalTime() { return totalTime; }}
执行结果示例:
结果表明,原子操作类在性能上明显优于加锁机制。
为了更深入理解原子操作的使用,我们可以创建一个自定义的递增方法,利用原子操作类实现 thread-safe 的计数器。
public class HalfAtomicInt { private AtomicInteger atomicI = new AtomicInteger(0); public void increment() { for ( ;; ) { int current = atomicI.get(); boolean success = atomicI.compareAndSet(current, current + 1); if (success) { break; } } } public int getCount() { return atomicI.get(); }}
这个实现使用了 compareAndSet
方法,在每次递增时会自动检验当前值,并原子性地更新到下一个值。
尽管原子操作类提供了高效、线程安全的方法,但它也有一些需要注意的地方:
CAS 机制可能导致某些场景下出现不一致性问题,例如:
为了解决 ABA 问题,引入了版本号机制:
性能开销
循环 CAS 操作虽然效率高,但如果操作次数大量多的话,可能会产生较大的性能开销。
适用范围
原子操作类只能直接控制一个共享变量。若需要管理多个变量或对象,需使用合成对象(如 AtomicReference
)。
例如,可以包装多个元数据到一个对象中,利用 AtomicReference
来控制这种对象的引用。
为了避免 ABA 问题,Java 提供了两种改进版本的原子引用类:
AtomicMarkableReference:
AtomicStampedReference:
例如:
public class AtomicStampedReferenceTest { public static void main(String[] args) throws InterruptedException { // 初始化参考对象和版本号 AtomicStampedReference
运行结果可以看到,不论是 Thread t1 还是 Thread t2,都能够正确地进行值交换,且版本号不断递增,确保一致性。
原子操作是一种强大的工具,能够在多线程环境下确保操作的原子性,从而避免数据不一致性问题。它通过利用硬件支持的 CAS 机制,提供了高效、低开销的线程安全保障。
通过合理的工具选择和正确的使用方法,原子操作可以在高并发场景中发挥巨大的作用。
转载地址:http://ypkhz.baihongyu.com/