CAS(Compare-And-Swap)是一种基于比较和交换原理的原子操作机制,用于实现无锁编程。它通过一系列方法(如 compareAndSwapObject
、compareAndSwapInt
、compareAndSwapLong
等)实现对变量的原子更新。这些方法的核心逻辑是:先比较当前值是否与预期值一致,如果一致,则将变量更新为新值,并返回 true
;否则,操作失败,返回 false
。这些方法的参数通常包括:目标对象、字段的内存偏移量、预期值和新值。
在底层实现中,CAS方法通过调用 native
方法来利用硬件指令完成操作。在x86架构中,CAS操作通常基于 cmpxchg
指令。然而,cmpxchg
指令本身并不具备跨多核CPU的原子性。为了确保在多核CPU环境下操作的原子性,通常需要结合 lock
前缀指令。lock
指令是一种CPU级别的锁,能够确保在多核环境下,当前操作对内存的访问是原子性的,从而避免并发冲突。
以下是一个使用 compareAndSwapInt
方法的示例,展示了如何通过 Unsafe
类实现原子操作
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class CASExample {
private static final Unsafe UNSAFE;
private static final long valueOffset;
static {
try {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
UNSAFE = (Unsafe) theUnsafe.get(null);
valueOffset = UNSAFE.objectFieldOffset(CASExample.class.getDeclaredField("value"));
} catch (Exception e) {
throw new RuntimeException("Failed to initialize Unsafe", e);
}
}
private volatile int value;
public boolean compareAndSwapInt(int expectedValue, int newValue) {
return UNSAFE.compareAndSwapInt(this, valueOffset, expectedValue, newValue);
}
public static void main(String[] args) {
CASExample example = new CASExample();
example.value = 10;
// 尝试将值从10更新为20
boolean success = example.compareAndSwapInt(10, 20);
System.out.println("Update success: " + success); // 输出:Update success: true
// 再次尝试将值从10更新为30,此时值已经是20,预期值不匹配
success = example.compareAndSwapInt(10, 30);
System.out.println("Update success: " + success); // 输出:Update success: false
}
}
CAS机制通过比较和交换操作实现了无锁编程中的原子性,避免了传统锁机制带来的线程阻塞问题,从而提高了并发性能。在底层,CAS操作通过硬件指令(如 cmpxchg
和 lock
)实现,确保了在多核CPU环境下的正确性和原子性。
在并发环境中,一个变量的值在CAS操作过程中发生了多次变化,最终又回到了初始值,导致CAS操作误认为变量未被修改,从而引发潜在的线程安全问题。具体来说,假设一个共享变量的值是A,线程1将其改为B,线程2又将B改回A,而线程3在执行CAS操作时,会误认为变量的值从未改变,从而可能基于错误的假设进行操作。
模拟示例代码如下:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class ABADemo {
private static final AtomicInteger atomicInteger = new AtomicInteger(100);
public static void main(String[] args) throws InterruptedException {
// 线程1:将A改为B
Thread thread1 = new Thread(() -> {
try {
// 模拟线程1执行时间较长
TimeUnit.SECONDS.sleep(1);
atomicInteger.compareAndSet(100, 200);
System.out.println("Thread1: 改变value值 100 为 200");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 线程2:将B改为A
Thread thread2 = new Thread(() -> {
try {
// 模拟线程2执行时间较长
TimeUnit.SECONDS.sleep(2);
atomicInteger.compareAndSet(200, 100);
System.out.println("Thread2: 改变value值 from 200 为 100");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 线程3:将A改为C
Thread thread3 = new Thread(() -> {
try {
// 模拟线程3执行时间较长
TimeUnit.SECONDS.sleep(3);
atomicInteger.compareAndSet(100, 300);
System.out.println("Thread3: 改变value值 from 100 为 300");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread1.start();
thread2.start();
thread3.start();
thread1.join();
thread2.join();
thread3.join();
System.out.println("最后的值: " + atomicInteger.get());
}
}
虽然最终值是300,但线程3的CAS操作成功了,而它并不知道值曾经被线程1和线程2修改过。这就是ABA问题的典型表现:线程3基于错误的假设(认为值从未改变)进行了操作。
引入一个版本号或者时间戳来记录变量的修改次数,Java提供了 AtomicStampedReference
类来解决ABA问题。AtomicStampedReference
使用一个整型的版本号来记录每次修改,从而避免了ABA问题。
import java.util.concurrent.atomic.AtomicStampedReference;
public class ABASolutionDemo {
private static final AtomicStampedReference sharedValue = new AtomicStampedReference<>(100, 0); // 初始值为A(100),版本号为0
public static void main(String[] args) throws InterruptedException {
// 线程1:将A改为B
Thread thread1 = new Thread(() -> {
int[] stampHolder = {sharedValue.getStamp()}; // 获取当前版本号
sharedValue.compareAndSet(100, 200, stampHolder[0], stampHolder[0] + 1); // 将A(100)改为B(200)
System.out.println("Thread1: 修改值为 100 到 200, stamp: " + sharedValue.getStamp());
});
// 线程2:将B改为A
Thread thread2 = new Thread(() -> {
int[] stampHolder = {sharedValue.getStamp()}; // 获取当前版本号
sharedValue.compareAndSet(200, 100, stampHolder[0], stampHolder[0] + 1); // 将B(200)改回A(100)
System.out.println("Thread2: 修改值为 200 到 100, stamp: " + sharedValue.getStamp());
});
// 线程3:将A改为C
Thread thread3 = new Thread(() -> {
int[] stampHolder = {sharedValue.getStamp()}; // 获取当前版本号
boolean success = sharedValue.compareAndSet(100, 300, stampHolder[0], stampHolder[0] + 1); // 尝试将A(100)改为C(300)
if (success) {
System.out.println("Thread3: 修改值为 100 到 300, stamp: " + sharedValue.getStamp());
} else {
System.out.println("Thread3: 修改 100 到 300失败, stamp: " + sharedValue.getStamp());
}
});
// 启动线程
thread1.start();
thread1.join(); // 确保线程1先执行
thread2.start();
thread2.join(); // 确保线程2在线程3之前执行
thread3.start();
thread3.join();
System.out.println("Final value: " + sharedValue.getReference());
System.out.println("Final stamp: " + sharedValue.getStamp());
}
}
CAS(Compare-And-Swap)操作是一种无锁的并发控制机制,通过不断尝试更新变量值来实现线程安全。然而,在高并发场景下,如果多个线程频繁竞争同一个变量,CAS操作可能会失败多次,导致线程进入“自旋”状态,即不断重试。这种自旋会占用大量CPU资源,降低系统性能
import java.util.concurrent.atomic.AtomicInteger;
public class CASSpinExample {
private static final AtomicInteger sharedValue = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
// 启动多个线程,模拟高并发场景
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
int expectedValue;
int newValue;
int spinCount = 0; // 记录自旋次数
do {
expectedValue = sharedValue.get();
newValue = expectedValue + 1;
spinCount++;
// 如果自旋次数过多,打印警告信息
if (spinCount > 10000) {
System.out.println("Thread " + Thread.currentThread().getId() + " is spinning too much!");
}
} while (!sharedValue.compareAndSet(expectedValue, newValue));
// 如果CAS失败,会一直重试,可能导致大量自旋
System.out.println("Thread " + Thread.currentThread().getId() + " completed after " + spinCount + " spins.");
}).start();
}
Thread.sleep(2000); // 等待所有线程执行完毕
System.out.println("Final value: " + sharedValue.get());
}
}
改进方法一:定义了一个常量MAX_SPIN_COUNT
,表示允许的最大自旋次数 ,当超出这个最大值,线程将不再CAS操作,直接退出线程
import java.util.concurrent.atomic.AtomicInteger;
public class CASSpinExample {
private static final AtomicInteger sharedValue = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
// 启动多个线程,模拟高并发场景
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
int expectedValue;
int newValue;
int spinCount = 0; // 记录自旋次数
final int MAX_SPIN_COUNT = 10000; // 设置最大自旋次数
do {
expectedValue = sharedValue.get();
newValue = expectedValue + 1;
spinCount++;
// 如果自旋次数超过限制,采取替代措施
if (spinCount > MAX_SPIN_COUNT) {
System.out.println("Thread " + Thread.currentThread().getId() + " is spinning too much! Giving up.");
// 选择退出线程或暂停一段时间
// Thread.sleep(100); // 暂停100毫秒
return; // 退出线程
}
} while (!sharedValue.compareAndSet(expectedValue, newValue));
// 如果CAS成功,打印完成信息
System.out.println("Thread " + Thread.currentThread().getId() + " completed after " + spinCount + " spins.");
}).start();
}
Thread.sleep(2000); // 等待所有线程执行完毕
System.out.println("Final value: " + sharedValue.get());
}
}
改进方法二:使用synchronized
或传统锁:在CAS操作失败多次后,退而使用传统的同步锁(如synchronized
或ReentrantLock
),以减少CPU资源浪费。
import java.util.concurrent.atomic.AtomicInteger;
public class CASWithLockFallback {
private static final AtomicInteger sharedValue = new AtomicInteger(0);
private static final Object lock = new Object();
private static final int MAX_SPIN_COUNT = 10000;
public static void main(String[] args) throws InterruptedException {
// 启动多个线程,模拟高并发场景
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
int expectedValue;
int newValue;
int spinCount = 0; // 记录自旋次数
do {
expectedValue = sharedValue.get();
newValue = expectedValue + 1;
spinCount++;
// 如果自旋次数超过限制,使用锁来完成操作
if (spinCount > MAX_SPIN_COUNT) {
synchronized (lock) {
sharedValue.incrementAndGet();
}
System.out.println("Thread " + Thread.currentThread().getId() + " used lock after " + spinCount + " spins.");
return; // 退出线程
}
} while (!sharedValue.compareAndSet(expectedValue, newValue));
// 如果CAS成功,打印完成信息
System.out.println("Thread " + Thread.currentThread().getId() + " completed after " + spinCount + " spins.");
}).start();
}
Thread.sleep(2000); // 等待所有线程执行完毕
System.out.println("Final value: " + sharedValue.get());
}
}
改进方法三:使用LongAdder
代替AtomicLong
:LongAdder
通过分段锁机制,将多个线程的操作分散到不同的分段上,从而减少冲突,提高性能。
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.LongAdder;
public class LongAdderExample {
private static final LongAdder sharedValue = new LongAdder();
public static void main(String[] args) throws InterruptedException {
// 启动多个线程,模拟高并发场景
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
// 使用LongAdder的increment方法进行线程安全的递增操作
sharedValue.increment();
System.out.println("Thread " + Thread.currentThread().getId() + " completed.");
}).start();
}
Thread.sleep(2000); // 等待所有线程执行完毕
System.out.println("Final value: " + sharedValue.sum());
}
}