你好,我是吴计可师,一个工作十多年的后端开发,曾就职京东、阿里等多家互联网头部企业。
文章可能会比较长,主要解析的非常详解,或涉及一些底层知识,供面试高阶难度用。可以根据自己实际理解情况合理取舍阅读
CAS(Compare-And-Swap,比较并交换)是一种常用的原子操作,用于实现无锁的并发控制,它是一种基于硬件的同步机制。CAS 是一种乐观锁技术,广泛应用于多线程编程中,尤其在高并发场景下,CAS 能够避免传统的加锁机制带来的性能开销。
内存位置:要修改的变量(如某个共享变量的内存地址)。
预期值(Expected Value):CAS 操作希望变量当前的值是什么。
新值(New Value):如果内存位置的当前值等于预期值,那么将其更新为新值。
CAS 的执行流程如下:
比较内存位置的当前值和预期值是否相等。
如果相等,则将内存位置的值更新为新值。
如果不相等,则不做任何操作,返回当前内存位置的值,表示操作失败。
CAS 是一种原子操作,即在操作执行过程中不会被线程调度中断,保证了并发环境下的线程安全。
AtomicInteger.compareAndSet(expectedValue, newValue):如果当前值等于 expectedValue,则将其更新为 newValue。
AtomicReference.compareAndSet(expectedReference, newReference):对引用类型进行原子比较和交换。
public class CASExample {
private static final AtomicInteger atomicInteger = new AtomicInteger(0);
public static void main(String[] args) {
// 假设当前 atomicInteger 的值为 0
int currentValue = atomicInteger.get(); // 获取当前值
int expectedValue = currentValue; // 假设预期值为当前值
int newValue = 100; // 新值为 100
// 进行 CAS 操作
boolean isUpdated = atomicInteger.compareAndSet(expectedValue, newValue);
System.out.println("CAS 操作成功: " + isUpdated);
System.out.println("当前值: " + atomicInteger.get());
}
}
在这个例子中,如果 atomicInteger 的当前值是 0,且 expectedValue 为 0,那么 CAS 操作就会成功,并且将 atomicInteger 更新为 newValue(即 100)。如果 atomicInteger 的值在此期间被其他线程修改了,则 CAS 操作失败。
无锁操作:CAS 是通过硬件支持的原子操作来保证线程安全,不需要加锁,避免了加锁带来的性能开销。
高并发性能:由于 CAS 是无锁的,它在多线程竞争时可以高效地执行,适用于频繁访问共享数据的场景。
ABA 问题:CAS 操作会判断预期值与当前值是否相等,如果值的变化是先变成另一个值,再恢复到原来的值,CAS 可能会误认为值没有变化,导致逻辑错误。这种问题称为 ABA 问题。
解决方法:可以通过使用带版本号的变量(如 AtomicStampedReference 或 AtomicMarkableReference)来解决 ABA 问题。
自旋阻塞:如果多个线程不断地竞争同一个变量,CAS 操作可能会导致自旋(即反复尝试操作),从而消耗 CPU 资源,特别是在高并发下,性能可能会受到影响。
只能操作单个变量:CAS 只能对一个变量进行原子操作,无法保证多个变量的原子性。在需要保证多个操作的原子性时,仍然需要加锁机制。
java.util.concurrent.atomic 包中的类:AtomicInteger、AtomicLong、AtomicReference 等,它们都通过 CAS 实现了对单一变量的原子操作。
无锁队列:一些并发数据结构(如无锁链表、无锁队列等)实现了基于 CAS 的并发控制。
并发算法:例如乐观锁(Optimistic Locking)、自旋锁等,CAS 操作常作为这些算法的基础。
ABA 问题:如果一个变量从 A 变为 B,又从 B 变回 A,CAS 认为值没变,可能导致错误。
解决方法:引入版本号,通过 AtomicStampedReference 或 AtomicMarkableReference 解决。
示例:
AtomicStampedReference<Integer> atomicStampedRef = new AtomicStampedReference<>(100, 1);
CAS 通过 CPU 的硬件指令(如 Intel 的 CMPXCHG)实现原子操作。
Java 中通过 Unsafe 类中的 compareAndSwap 方法调用硬件指令完成。
CAS | Synchronized |
---|---|
无锁机制,基于硬件原子操作实现。 | 基于 JVM 的重量级锁。 |
性能较高,但需要解决 ABA 问题。 | 性能较低,但没有 ABA 问题。 |
适合高并发场景,如计数器等原子操作。 | 适合需要确保多操作原子性的场景。 |
原子变量:如 AtomicInteger、AtomicLong。
高并发队列:如 ConcurrentLinkedQueue。
乐观锁:如数据库的版本控制。
自定义锁:如自旋锁的实现。
设定自旋次数限制:如尝试一定次数后切换为阻塞锁。
使用 Thread.yield() 或 LockSupport.park() 暂时挂起线程。
调整业务逻辑,减少共享资源竞争。
CAS 不能直接保证多个共享变量的原子性操作。
可以使用锁(如 synchronized 或 ReentrantLock)来保证整体操作的原子性。
或者通过组合数据结构(如将多个变量封装为对象并使用 AtomicReference)。
自旋锁是一种无锁的同步机制,线程尝试不断检查资源状态,直到成功获得锁。
CAS 是实现自旋锁的核心原子操作。
class SpinLock {
private AtomicReference<Thread> owner = new AtomicReference<>();
public void lock() {
Thread currentThread = Thread.currentThread();
while (!owner.compareAndSet(null, currentThread)) {
// 自旋等待
}
}
public void unlock() {
Thread currentThread = Thread.currentThread();
owner.compareAndSet(currentThread, null);
}
}
CAS 不涉及线程上下文切换,减少了内核态和用户态的切换开销。
锁机制会导致线程阻塞和唤醒,增加性能开销。
在低锁竞争场景下,CAS 更高效。
今天分享的内容就到这儿,喜欢的朋友欢迎点赞关注!你的关注是我前进的动力