Java AtomicLong详解与应用实战

166 阅读5分钟

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 的典型应用场景

  1. 线程安全的计数器​:这是 AtomicLong 最经典的应用,例如统计网站访问量、API调用次数等 。

    public class PageViewCounter {
        private final AtomicLong count = new AtomicLong(0);
    
        public void onPageVisit() {
            count.incrementAndGet();
            // 可以定期将 count 的值持久化或上报
        }
    
        public long getCount() {
            return count.get();
        }
    }
    
  2. 序列号/唯一ID生成器​:在单机或集群环境中(配合其他标识),可以利用其原子递增性生成唯一序列号,如订单ID、日志序列号等 。

    public class IdGenerator {
        private static final AtomicLong idSequence = new AtomicLong(1000); // 从1000开始
    
        public static long getNextId() {
            return idSequence.incrementAndGet();
        }
    }
    
  3. 状态指示器​:用于表示程序的状态,例如下载进度、任务完成的百分比等 。由于 get()操作是直接读取 volatile 变量,性能很高,适合监控。

  4. 实现简单的自旋锁​:虽然在实际开发中更推荐使用 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);
        }
    }
    

⚠️ 使用注意事项与局限性

  1. ABA 问题​:CAS 操作可能会遇到 ABA 问题。即一个变量的值从 A 变为 B,然后又变回 A。那么 CAS 在检查时会误以为它没有改变过。对于一些敏感的场景(如链表的节点操作),这可能存在问题。解决方案是使用带有版本号的原子引用类 AtomicStampedReference
  2. 高竞争下的性能瓶颈​:在并发量极高的场景下,大量线程会同时竞争修改同一个变量,导致 CAS 操作频繁失败并长时间自旋,反而会消耗大量 CPU 资源 。此时,AtomicLong 的性能会显著下降。

🔄 AtomicLong 与 LongAdder 的对比与选型

针对高并发下的性能瓶颈,Java 8 引入了 LongAdder

特性维度AtomicLongLongAdder
设计思想单一变量,全局竞争内部维护一个 base变量和一个 Cell[]数组,分散竞争 。
写性能(高并发)​较差,CAS竞争激烈极佳,通过分散热点提升吞吐量 。
读性能(get())​,一次 volatile 读较低,需要累加 base和所有 Cell的值,复杂度为 O(n) 。
数据一致性强一致性,每次读取都是最新精确值 。最终一致性sum()返回的是某个时刻的近似值,非原子快照 。
内存占用较大(空间换时间) 。
适用场景低至中度并发;需要频繁读取且要求精确值;需要原子性复合操作​(如 compareAndSet) 。超高并发写入,对读的实时精确性要求不高(如统计计数器、监控指标收集) 。

选型建议​:

  • 追求高并发写性能,并且可以接受最终一致性的读取​:选择 LongAdder。例如,统计 QPS、错误次数等 。
  • 需要强一致性,或者需要依赖精确值的原子操作(如 CAS)​​:选择 AtomicLong。例如,生成全局唯一序列号、实现状态机等 。
  • 低并发或竞争不激烈​:两者性能差异不大,AtomicLong的 API 更丰富,内存占用更小,可能是更简单的选择 。

💎 总结

AtomicLong 是 Java 并发工具箱中一把精准而高效的“手术刀”,它通过 CAS 操作实现了无锁的线程安全,在低至中度并发场景下表现出色。理解其原理、熟练掌握其 API 并明确其与 LongAdder 的适用边界,将帮助你在实际项目中做出最合适的技术选型,构建出既正确又高性能的并发程序。