JUC并发编程(六)CAS无锁实现/原子整数/原子引用/原子数组/字段更新
目录
一 CAS与volatile
1 CAS(Compare and Swap)
2 volatile
3 为什么无锁效率高
二 原子整数
AtomicBoolean
AtomicInteger
AtomicLong
三 原子引用
AtomicReference
AtomicMarkablereference
AtomicStampedReference
四 原子数组
AtomicIntegerArray
AtomicLongArray
AtomicReferenceArray
四 字段更新器
AtomicRederenceFieldUpdater
AtomicIntegerFieldUpdater
AtomicLongFieldUpdater
五 原子累加器-LongAdder
六 Unsafe(了解)
一 CAS与volatile
结合CAS和volatile可以实现无锁并发,适用于线程数少,多核CPU的情况下。
- CAS是乐观锁的思想:不怕别的线程来修改共享变量,就算修改了也没关系,读取时比较一下,不一样再重试一次。
- synchroized是基于悲观锁的思想:需要防止其他线程在读取时修改变量。
1 CAS(Compare and Swap)
-
无锁并发:避免线程阻塞(对比
synchronized
节省上下文切换开销) -
高吞吐量:适用于低竞争场景(实测比锁快2-5倍)
-
避免死锁:无锁机制天然免疫死锁
CAS通过硬件指令,原子性地比较内存地址处的当前二进制值是否等于预期值,相等则更新为新值,否则重新获取最新值并重试。
- 演示
比如说现在需要取钱,有一个读取值和修改后的值。在调用compareandSet时,会将这个读取值与现在实际的值进行对比如果出现不同说明在这期间有其他线程对值进行修改,这样的话将会重试,再次读取,再次比较,再次判断以达到最终的效果。
2 volatile
- 访问共享变量时,使用
volatile
修饰可确保可见性。 - 局限性:无法解决复合操作的原子性和指令交错问题。
CAS必须借助volatile才能读取到共享变量的最新值来实现【比较并切换】的效果。
3 为什么无锁效率高
无锁情况下,及时重试失败线程始终在高速运行,而synchroized会让线程在没有获得锁的时候,发生上下文切换,进入阻塞。
无锁编程通过消除阻塞、减少内核交互和利用硬件原子指令,最大化CPU利用率与缓存效率。但其优势高度依赖场景:在低冲突、细粒度操作中性能显著,而在高冲突或复杂操作中需谨慎评估。
二 原子整数
-
无锁线程安全:基于 CAS (Compare-And-Swap) 实现,避免传统锁的性能开销
-
原子操作:所有操作都是原子的,不会被线程调度打断
-
内存可见性:保证修改后对其他线程立即可见
-
高性能:特别适合高并发、低冲突场景
JUC并发包提供了:
-
AtomicBoolean
// 创建AtomicBoolean flag = new AtomicBoolean(true);// 获取当前值boolean current = flag.get();// 设置新值flag.set(false);// 比较并设置(CAS)boolean success = flag.compareAndSet(true, false); // 当前值为true时更新为false// 获取并设置boolean previous = flag.getAndSet(true);
-
AtomicInteger
// 原子类AtomicInteger counter = new AtomicInteger(0);// 基础操作int val = counter.get();counter.set(10);// 原子自增int pre = counter.getAndIncrement(); // i++int post = counter.incrementAndGet(); // ++i// 原子自减int pre1 = counter.getAndDecrement(); // i--int post1 = counter.decrementAndGet(); // --i// 原子加法int pre2 = counter.getAndAdd(5); // 加5,返回旧值int post2 = counter.addAndGet(5); // 加5,返回新值// CAS操作boolean updated = counter.compareAndSet(10, 20);// 函数式更新int result1 = counter.updateAndGet(x -> x * 2);int result2 = counter.getAndUpdate(x -> x * 2);
-
AtomicLong
AtomicLong bigCounter = new AtomicLong(0);// 与AtomicInteger类似的方法long val = bigCounter.get();bigCounter.set(1000L);long pre = bigCounter.getAndIncrement();// i++long post = bigCounter.incrementAndGet();// ++ilong pre1 = bigCounter.getAndAdd(100L);// 加100,返回旧值long post1 = bigCounter.addAndGet(100L);// 加100,返回新值boolean updated = bigCounter.compareAndSet(1000L, 2000L);// CAS操作// 特殊方法(Java8+)long result = bigCounter.accumulateAndGet(50, (x, y) -> x * y);
三 原子引用
在并发编程中,除了基本数据类型的原子操作,我们还需要对对象引用进行原子操作。JUC 提供了三种重要的原子引用类:
-
AtomicReference
// 创建原子引用(这里使用的数据类型为String)AtomicReference<String> ref = new AtomicReference<>("initial");// 基础操作String value = ref.get(); // 获取当前值ref.set("new value"); // 设置新值// CAS操作boolean success = ref.compareAndSet("expected", "new value");// 函数式更新ref.updateAndGet(current -> current + " updated");ref.getAndUpdate(current -> current + " updated");
-
AtomicMarkablereference
// 创建带标记的引用(初始值"A",初始标记false)AtomicMarkableReference<String> marketableRef = new AtomicMarkableReference<>("A", false);// 获取引用和标记boolean[] markHolder = new boolean[1];String ref = marketableRef.get(markHolder); // markHolder[0]获取标记 0 , ref获取引用 A// 带标记的CAS操作boolean success = marketableRef.compareAndSet("A", // 预期引用"B", // 新引用false, // 预期标记true // 新标记);
-
AtomicStampedReference
问题引入:
ABA 问题是并发编程中 CAS 机制的“隐形陷阱”,其核心矛盾在于:变量值形式上恢复原值,但内在状态已变化。通过引入版本号、状态标记或不可变对象,可以有效避免此类问题。在实际开发中,优先使用 Java 提供的原子工具类(如 AtomicStampedReference
)来应对 ABA 场景。
- 引用值(
currentRef
):直接返回当前引用的值(如String
、Object
等)。 - 版本号(
stampHolder[0]
):通过修改传入的int[]
数组的元素值,将版本号“返回”给调用者。
// 创建带版本戳的引用(初始值"A",初始版本0)AtomicStampedReference<String> stampedRef = new AtomicStampedReference<>("A", 0);// 获取引用和版本戳int[] stampHolder = new int[1];String currentRef = stampedRef.get(stampHolder); // stampHolder[0]获取版本号 0 currentRef获取引用对象System.out.println("Current reference: " + currentRef);// 输出:Current reference: ASystem.out.println("Current stamp: " + stampHolder[0]);// 输出:Current stamp: 0// 带版本戳的CAS操作boolean success = stampedRef.compareAndSet("A", // 预期引用"B", // 新引用0, // 预期版本戳1 // 新版本戳);
四 原子数组
在并发编程中,当需要对数组元素进行原子操作时,Java 提供了专门的原子数组类。
-
AtomicIntegerArray
// 创建长度为10的原子int数组(初始值为0)AtomicIntegerArray intArray = new AtomicIntegerArray(10);// 通过现有数组创建int[] init = {1, 2, 3, 4, 5};AtomicIntegerArray array = new AtomicIntegerArray(init);// 获取索引i处的值int val = array.get(0); // 返回索引0的值// 设置索引i的值array.set(1, 10); // 将索引1设为10// 原子设置并返回旧值int old = array.getAndSet(2, 20); // 设置索引2=20,返回旧值// CAS操作boolean success = array.compareAndSet(3, 4, 40); // 如果索引3=4则设为40// 原子自增int pre = array.getAndIncrement(4); // i++ int post = array.incrementAndGet(4); // ++i// 原子加法int preAdd = array.getAndAdd(0, 5); // 索引0加5,返回旧值int postAdd = array.addAndGet(0, 5); // 索引0加5,返回新值// 函数式更新(Java8+)int result = array.updateAndGet(1, x -> x * 2); // 索引1的值乘以2int andUpdate = array.getAndUpdate(2, x -> x * 2);// 索引2的值乘以2
-
AtomicLongArray
使用方式与Integer的类似
AtomicLongArray timeStamps = new AtomicLongArray(100);// 记录事件发生时间
void recordEvent(int eventId) {timeStamps.set(eventId, System.currentTimeMillis());
}// 原子更新最近时间
void updateLatest(int eventId, long newTime) {timeStamps.updateAndGet(eventId, old -> Math.max(old, newTime));
}
-
AtomicReferenceArray
// 创建长度为5的对象引用数组
AtomicReferenceArray<String> refArray = new AtomicReferenceArray<>(5);// 设置元素
refArray.set(0, "first");// CAS操作
boolean success = refArray.compareAndSet(1, null, "second");// 获取并设置
String old = refArray.getAndSet(2, "third");// 函数式更新
refArray.updateAndGet(3, s -> s != null ? s.toUpperCase() : "DEFAULT");
四 字段更新器
“字段更新器”通常指的是在编程中,用于封装和集中管理对象字段更新逻辑的一种设计模式或工具类。它的核心目的是提供一种更安全、更灵活、更集中(有时也更高效)的方式来修改对象的内部状态(字段)。
利用字段更新器,可以针对对象的某个域进行原子操作,只能配合volatile修饰的字段使用,否则会出现异常
-
AtomicRederenceFieldUpdater
class Node {volatile Node next; // 必须是 volatile 引用
}// 创建更新器
AtomicReferenceFieldUpdater<Node, Node> updater = AtomicReferenceFieldUpdater.newUpdater(Node.class, Node.class, "next");Node nodeA = new Node();
Node nodeB = new Node();// 原子更新:将 nodeA.next 从 null 改为 nodeB
updater.compareAndSet(nodeA, null, nodeB);
-
AtomicIntegerFieldUpdater
class Counter {volatile int count; // volatile int 字段
}// 创建更新器
AtomicIntegerFieldUpdater<Counter> updater = AtomicIntegerFieldUpdater.newUpdater(Counter.class, "count");Counter counter = new Counter();// 原子增加
updater.incrementAndGet(counter); // 相当于 ++counter.count
updater.addAndGet(counter, 5); // counter.count += 5
-
AtomicLongFieldUpdater
class BankAccount {volatile long balance; // 原子更新的 long 字段
}// 原子转账操作
AtomicLongFieldUpdater<BankAccount> updater = AtomicLongFieldUpdater.newUpdater(BankAccount.class, "balance");BankAccount account = new BankAccount();
updater.getAndAdd(account, 1000); // 存入 1000
五 原子累加器-LongAdder
性能提升:空间换时间
维护一个base+多个cell[](累加单元),Thread0累加了cell[0]而Thread1累加了cell[1]...最后将结果汇总。在累加时操作不同的Cell变量,将少了CAS重试失败的次数,从而提高性能。
// 创建
LongAdder adder = new LongAdder();// 累加操作
adder.add(5); // 增加指定值
adder.increment(); // +1 (等价于add(1))
adder.decrement(); // -1 (等价于add(-1))// 获取结果
long sum = adder.sum(); // 当前总和(非原子快照)
long temp = adder.longValue(); // 等同于sum()// 重置
adder.reset(); // 归零(保留Cell结构)
long s = adder.sumThenReset(); // 获取总和后归零
六 Unsafe(了解)
sun.misc.Unsafe
是 Java 标准库中一个强大但极其危险的类,它提供了直接操作内存、绕过 JVM 安全机制的能力,被称为 "Java 的后门"。尽管官方不推荐使用,但它却是许多高性能框架(如 Netty、Disruptor、Cassandra)的核心基础。
由于设计者不希望开发者随意使用,获取 Unsafe 需要特殊技巧:
import sun.misc.Unsafe;import java.lang.reflect.Field;public class UnsafeAccessor {private static final Unsafe UNSAFE;static {try {Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");theUnsafe.setAccessible(true);UNSAFE = (Unsafe) theUnsafe.get(null);} catch (Exception e) {throw new Error(e);}}public static Unsafe getUnsafe() {return UNSAFE;}
}
与原子类(如 AtomicInteger)的关系
角色 | Unsafe | 原子类(AtomicXXX) |
---|---|---|
底层实现 | 提供 compareAndSwapInt() 等 CPU 原子指令 | 调用 Unsafe 的 CAS 实现线程安全操作 |
内存可见性 | 直接操作内存地址 | 基于 volatile + Unsafe 保证可见性 |
定位 | JVM 级原子操作(危险) | 开发者友好的安全封装 |
关系本质:原子类是 Unsafe 的安全包装