AtomicLong 是 Java 并发编程中一个非常重要的原子类,它通过无锁的方式实现了线程安全的长整型操作。下面我将从核心原理、关键方法、应用场景以及与 LongAdder 的对比等方面,为你详细解析 AtomicLong。
⚙️ AtomicLong 的核心原理与优势
AtomicLong 的核心在于利用 CAS(Compare-And-Swap) 操作来保证原子性,其内部依赖于 sun.misc.Unsafe类提供的底层原子操作能力 。
-
CAS 操作流程:其基本工作模式是循环进行“读取-计算-比较并交换”。线程会先读取当前值,计算新值,然后尝试通过 CAS 操作更新。如果此时当前值仍等于之前读取的值(说明未被其他线程修改),则更新成功;否则,操作失败并循环重试,直到成功为止 。例如,
incrementAndGet()方法的典型实现如下:public final long incrementAndGet() { while (true) { long current = get(); // 读取当前值 long next = current + 1; // 计算新值 if (compareAndSet(current, next)) { // 尝试CAS更新 return next; } // 如果CAS失败,则循环重试 } } -
内存可见性:AtomicLong 内部用于存储值的
value字段使用volatile关键字修饰 。这确保了当一个线程修改了该值后,新值能立即对其他线程可见,防止读到脏数据。 -
与 synchronized 的对比:相比于传统的
synchronized关键字,AtomicLong 采用的无锁(Lock-Free) 策略在高并发场景下通常能提供更好的性能,因为它避免了线程阻塞和上下文切换带来的巨大开销 。
📚 AtomicLong 的关键方法解析
AtomicLong 提供了一系列常用的原子操作方法,下表进行了归纳:
| 方法类别 | 方法签名 | 功能描述 |
|---|---|---|
| 基础获取与设置 | long get() | 获取当前值 。 |
void set(long newValue) | 设置为新值(具备 volatile 写的内存语义) 。 | |
| 原子递增/递减 | long getAndIncrement() | 先返回当前值,然后自增1(类似 i++) 。 |
long incrementAndGet() | 先自增1,然后返回新值(类似 ++i) 。 | |
long getAndDecrement() | 先返回当前值,然后自减1(类似 i--) 。 | |
long decrementAndGet() | 先自减1,然后返回新值(类似 --i) 。 | |
| 原子加法/减法 | long getAndAdd(long delta) | 先返回当前值,然后加上指定的增量 。 |
long addAndGet(long delta) | 先加上指定的增量,然后返回新值 。 | |
| 条件更新(核心) | boolean compareAndSet(long expect, long update) | 核心方法。如果当前值等于预期值 expect,则原子性地更新为 update。 |
| 原子更新 | long getAndSet(long newValue) | 原子性地设置为新值,并返回旧值 。 |
| 函数式更新 (Java 8+) | long updateAndGet(LongUnaryOperator updateFunction) | 使用给定的函数原子性地更新当前值,并返回新值 。 |
💡 AtomicLong 的典型应用场景
-
线程安全的计数器:这是 AtomicLong 最经典的应用,例如统计网站访问量、API调用次数等 。
public class PageViewCounter { private final AtomicLong count = new AtomicLong(0); public void onPageVisit() { count.incrementAndGet(); // 可以定期将 count 的值持久化或上报 } public long getCount() { return count.get(); } } -
序列号/唯一ID生成器:在单机或集群环境中(配合其他标识),可以利用其原子递增性生成唯一序列号,如订单ID、日志序列号等 。
public class IdGenerator { private static final AtomicLong idSequence = new AtomicLong(1000); // 从1000开始 public static long getNextId() { return idSequence.incrementAndGet(); } } -
状态指示器:用于表示程序的状态,例如下载进度、任务完成的百分比等 。由于
get()操作是直接读取 volatile 变量,性能很高,适合监控。 -
实现简单的自旋锁:虽然在实际开发中更推荐使用 JUC 包中的高级锁,但理解其原理有助于深入学习并发 。
public class SimpleSpinLock { private final AtomicLong lockHolder = new AtomicLong(0); // 0表示锁空闲,非0表示被占用 public void lock() { long currentThreadId = Thread.currentThread().getId(); // 自旋,直到成功将锁持有者设置为当前线程ID while (!lockHolder.compareAndSet(0, currentThreadId)) { // 可选:短暂让出CPU时间片,减少CPU占用 Thread.yield(); } } public void unlock() { long currentThreadId = Thread.currentThread().getId(); // 只有锁的持有者才能释放锁 lockHolder.compareAndSet(currentThreadId, 0); } }
⚠️ 使用注意事项与局限性
- ABA 问题:CAS 操作可能会遇到 ABA 问题。即一个变量的值从 A 变为 B,然后又变回 A。那么 CAS 在检查时会误以为它没有改变过。对于一些敏感的场景(如链表的节点操作),这可能存在问题。解决方案是使用带有版本号的原子引用类
AtomicStampedReference。 - 高竞争下的性能瓶颈:在并发量极高的场景下,大量线程会同时竞争修改同一个变量,导致 CAS 操作频繁失败并长时间自旋,反而会消耗大量 CPU 资源 。此时,AtomicLong 的性能会显著下降。
🔄 AtomicLong 与 LongAdder 的对比与选型
针对高并发下的性能瓶颈,Java 8 引入了 LongAdder。
| 特性维度 | AtomicLong | LongAdder |
|---|---|---|
| 设计思想 | 单一变量,全局竞争 | 内部维护一个 base变量和一个 Cell[]数组,分散竞争 。 |
| 写性能(高并发) | 较差,CAS竞争激烈 | 极佳,通过分散热点提升吞吐量 。 |
读性能(get()) | 高,一次 volatile 读 | 较低,需要累加 base和所有 Cell的值,复杂度为 O(n) 。 |
| 数据一致性 | 强一致性,每次读取都是最新精确值 。 | 最终一致性,sum()返回的是某个时刻的近似值,非原子快照 。 |
| 内存占用 | 小 | 较大(空间换时间) 。 |
| 适用场景 | 低至中度并发;需要频繁读取且要求精确值;需要原子性复合操作(如 compareAndSet) 。 | 超高并发写入,对读的实时精确性要求不高(如统计计数器、监控指标收集) 。 |
选型建议:
- 追求高并发写性能,并且可以接受最终一致性的读取:选择
LongAdder。例如,统计 QPS、错误次数等 。 - 需要强一致性,或者需要依赖精确值的原子操作(如 CAS):选择
AtomicLong。例如,生成全局唯一序列号、实现状态机等 。 - 低并发或竞争不激烈:两者性能差异不大,
AtomicLong的 API 更丰富,内存占用更小,可能是更简单的选择 。
💎 总结
AtomicLong 是 Java 并发工具箱中一把精准而高效的“手术刀”,它通过 CAS 操作实现了无锁的线程安全,在低至中度并发场景下表现出色。理解其原理、熟练掌握其 API 并明确其与 LongAdder 的适用边界,将帮助你在实际项目中做出最合适的技术选型,构建出既正确又高性能的并发程序。