什么是CAS
CAS(Compare and Swap,比较并交换)是一种乐观锁技术,用于实现多线程环境下的原子操作。CAS操作包括三个参数:内存地址V、期望值A和新值B。当且仅当V的值等于A时,才将V的值设置为B,否则什么也不做。即针对一个变量,首 先比较它的内存值与某个期望值是否相同,如果相同,就给它赋一个新值。
CAS 的逻辑用伪代码描述如下:
if (value == expectedValue){
2 value = newValue; 3
}
以上伪代码描述了一个由比较和赋值两阶段组成的复合操作。
CAS操作是由硬件指令支持的,因此具有很高的性能和安全性。在Java中,CAS操作通常使用Atomic
类来实现,例如AtomicInteger
、AtomicLong
、AtomicReference
等。
CAS操作可以用于解决多线程环境下的竞态条件问题,例如计数器、队列、缓存等数据结构的实现。但是,CAS操作也存在一些限制,例如ABA问题、自旋开销大、并发度不高等,需要在使用时进行注意和处理。
CAS应用
在Java中,CAS操作是由Unsafe
类提供支持的。Unsafe
类是JDK中不稳定、不建议使用的类,但是它提供了一些底层操作方法,如CAS操作,用于支持Java并发编程。
Unsafe
类提供了几种针对不同类型变量的CAS操作,包括:
compareAndSwapObject(Object obj, long offset, Object expect, Object update)
:用于对象引用类型的CAS操作。compareAndSwapInt(Object obj, long offset, int expect, int update)
:用于整型类型的CAS操作。compareAndSwapLong(Object obj, long offset, long expect, long update)
:用于长整型类型的CAS操作。
它们都是 native 方法,由 Java 虚拟机提供具体实现,这意味着不同的 Java 虚拟机对它们的实现 可能会略有不同。
CAS操作通常通过Atomic
类来实现,例如AtomicInteger
、AtomicLong
、AtomicReference
等。这些类提供了一系列原子操作方法,可以保证线程安全地进行加减、比较、设置等操作。
以下是一些常见的CAS操作方法:
compareAndSet(expectedValue, newValue)
:如果当前值等于期望值,则将其设置为新值,并返回true;否则不做任何事情,并返回false。getAndIncrement()
:获取当前值,并将其增加1,返回增加前的值。getAndAdd(delta)
:获取当前值,并将其增加delta, 返回增加前的值。getAndSet(newValue)
:获取当前值,并将其设置为新值,返回设置前的值。
这些方法都是原子操作,可以确保线程安全。需要注意的是,在使用CAS操作时有可能会出现ABA问题(即在操作过程中值先被A修改成B,再被B修改成A),因此需要根据具体情况进行评估和处理。
CAS缺陷
- ABA问题:如果一个值在执行CAS操作前后都被改为了相同的值,那么CAS就可能误认为它没有发生改变。这种情况称为ABA问题。
@Slf4j public class ABATest { public static void main(String[] args) { AtomicInteger atomicInteger = new AtomicInteger(1); new Thread(()->{ int value = atomicInteger.get(); log.debug("Thread1 read value: " + value); // 阻塞1s LockSupport.parkNanos(1000000000L); // Thread1通过CAS修改value值为3 if (atomicInteger.compareAndSet(value, 3)) { log.debug("Thread1 update from " + value + " to 3"); } else { log.debug("Thread1 update fail!"); } },"Thread1").start(); new Thread(()->{ int value = atomicInteger.get(); log.debug("Thread2 read value: " + value); // Thread2通过CAS修改value值为2 if (atomicInteger.compareAndSet(value, 2)) { log.debug("Thread2 update from " + value + " to 2"); // do something value = atomicInteger.get(); log.debug("Thread2 read value: " + value); // Thread2通过CAS修改value值为1 if (atomicInteger.compareAndSet(value, 1)) { log.debug("Thread2 update from " + value + " to 1"); } } },"Thread2").start(); }
- 自旋开销:CAS操作是基于忙等待的自旋机制实现的,如果长时间无法竞争到资源,则会一直循环等待,浪费CPU资源。
- 并发度不高:在高并发场景下,由于CAS操作需要修改共享变量的值,因此只能有一个线程获得锁,导致并发度不高。
- 无法保证公平性:由于CAS操作是基于忙等待的自旋机制实现的,因此不能保证线程获取锁的公平性。
针对上述问题,可以采取一些措施进行处理和优化,例如使用带版本号的CAS操作来解决ABA问题、设置自旋次数或者使用阻塞等待来避免自旋开销、使用分段锁或者其他锁机制提高并发度、使用公平锁来确保锁的公平性等。需要根据具体情况进行评估和处理。