文章目录
- 1. 核心原理:CAS (Compare-And-Swap)
- 什么是 CAS
- Java 中的 Unsafe 类
- 2. 原子类家族谱系
- 3. 实战演练:AtomicInteger
- 场景:多线程计数器
- 错误示范:普通 int
- 正确示范:AtomicInteger
- 源码剖析:getAndIncrement
- 5. 高性能利器:LongAdder (JDK 8+)
- 设计思想:分段锁(空间换时间)
- AtomicLong vs LongAdder 性能对比示意
- 代码示例
在多线程并发编程的浩瀚宇宙中,线程安全始终是核心议题。当我们需要对一个变量进行简单的累加操作时,volatile关键字虽然能保证可见性,却无法保证原子性;而synchronized锁虽然强大,但在高并发场景下往往伴随着沉重的上下文切换开销。
这时,Java原子类(Atomic Classes)应运而生。它们位于java.util.concurrent.atomic包下,利用底层硬件的 CAS 机制,以一种无锁(Lock-Free)的高效方式,解决了并发场景下的原子性问题。
1. 核心原理:CAS (Compare-And-Swap)
原子类的魔法源自于CAS。它是一条 CPU 并发原语。
什么是 CAS
关于CAS的原理可以看这篇:https://blog.csdn.net/Tracycoder/article/details/156860586?spm=1011.2415.3001.10575&sharefrom=mp_manage_link
Java 中的 Unsafe 类
Java 无法直接访问底层操作系统,而是通过sun.misc.Unsafe类来操作内存。原子类内部正是调用了 Unsafe 的compareAndSwap系列方法。
2. 原子类家族谱系
JDK 提供了丰富的原子类,主要可以分为以下四类:
| 分类 | 包含类 | 描述 |
|---|---|---|
| 基本类型 | AtomicInteger,AtomicLong,AtomicBoolean | 直接更新基本类型数据。 |
| 数组类型 | AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray | 原子更新数组中的某个元素。 |
| 引用类型 | AtomicReference,AtomicStampedReference,AtomicMarkableReference | 原子更新对象引用,解决ABA问题。 |
| 对象属性 | AtomicIntegerFieldUpdater,AtomicLongFieldUpdater,AtomicReferenceFieldUpdater | 原子更新对象的某个字段。 |
3. 实战演练:AtomicInteger
AtomicInteger是最常用的原子类。让我们对比一下使用int和AtomicInteger在多线程下的表现。
场景:多线程计数器
假设有 100 个线程,每个线程对计数器累加 1000 次。
错误示范:普通 int
publicclassUnsafeCounter{privatestaticintcount=0;publicstaticvoidmain(String[]args)throwsInterruptedException{Thread[]threads=newThread[100];for(inti=0;i<100;i++){threads[i]=newThread(()->{for(intj=0;j<1000;j++){count++;// 非原子操作:读 -> 改 -> 写}});threads[i].start();}for(Threadt:threads)t.join();// 预期结果:100000,实际结果通常小于 100000System.out.println("Final Count (Unsafe): "+count);}}正确示范:AtomicInteger
importjava.util.concurrent.atomic.AtomicInteger;publicclassSafeCounter{// 使用原子类privatestaticAtomicIntegeratomicCount=newAtomicInteger(0);publicstaticvoidmain(String[]args)throwsInterruptedException{Thread[]threads=newThread[100];for(inti=0;i<100;i++){threads[i]=newThread(()->{for(intj=0;j<1000;j++){// 相当于 i++,但在硬件层面保证原子性atomicCount.getAndIncrement();}});threads[i].start();}for(Threadt:threads)t.join();// 结果始终为 100000System.out.println("Final Count (Atomic): "+atomicCount.get());}}源码剖析:getAndIncrement
AtomicInteger的getAndIncrement是如何实现的?
// JDK 8 源码片段publicfinalintgetAndIncrement(){// this: 当前对象// valueOffset: value 字段在内存中的偏移量// 1: 增加的值returnunsafe.getAndAddInt(this,valueOffset,1);}// Unsafe 类中的实现 (模拟)publicfinalintgetAndAddInt(Objecto,longoffset,intdelta){intv;do{// 1. 获取内存中当前的值v=getIntVolatile(o,offset);// 2. 循环尝试 CAS 操作// 如果内存值还是 v,则更新为 v + delta,返回 true 退出循环// 否则(说明被其他线程改了),返回 false,继续循环(自旋)}while(!compareAndSwapInt(o,offset,v,v+delta));returnv;}5. 高性能利器:LongAdder (JDK 8+)
虽然AtomicLong使用 CAS 保证了原子性,但在超高并发下,大量线程同时竞争更新同一个变量,会导致大量 CAS 失败并自旋,消耗 CPU 资源。
JDK 8 引入了LongAdder。
设计思想:分段锁(空间换时间)
LongAdder内部维护了一个base变量和一个Cell[]数组。
- 无竞争时:直接更新
base。 - 有竞争时:线程被哈希映射到
Cell数组的某个槽位,对该槽位的值进行 CAS 更新。 - 获取最终结果时:
sum = base + sum(Cell[])。
AtomicLong vs LongAdder 性能对比示意
代码示例
importjava.util.concurrent.atomic.LongAdder;publicclassLongAdderDemo{publicstaticvoidmain(String[]args){LongAdderadder=newLongAdder();// 多个线程同时累加adder.increment();adder.add(10);// 获取总和System.out.println("Sum: "+adder.sum());}}注意:LongAdder的sum()方法返回的值只是一个近似准确值(在并发极高时,统计过程中可能有新的累加发生),但在统计计数等场景下完全够用且性能极佳。