JUC: CAS
Compare And Swap, 比较并交换,实现并发算法时常用的一种技术。
它包含三个操作数:
- 内存位置
- 预期原值
- 更新值
执行CAS操作的时候,将内存位置的值与预期原值进行比较
- 如果相等,那么处理器会自动将该位置的值更新
- 如果不匹配,处理器不做任何操作,多线程同时执行CAS只会有一个成功
CAS是JDK提供的非阻塞原子性操作,它通过硬件保证了比较-更新的原子性它是非阻塞的且自身具有原子性,也就是说这玩意效率更高且通过硬件保证,说明这玩意更可靠。
CAS是一条CPU的原子指令(cmpxchg指令),不会造成所谓的数据不一致问题,Unsafe提供的CAS方法(如compareAndSwapXXX)底层实现即为CPU指令cmpxchg。
执行cmpxchg指令的时候,会判断当前系统是否为多核系统,如果是就给总线加锁,只有一个线程会对总线加锁成功,加锁成功之后会执行cas操作,也就是说CAS的原子性实际上是CPU实现独占的,比起用synchronized重量级锁,这里的排他时间要短很多,所以在多线程情况下性能会比较好。
1 Unsafe类
Unsafe是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。Unsfe类存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因为Java中CAS操作的执行依赖于Unsafe类的方法。
注意: Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务
JVM提供的CAS机制,会在汇编层级禁止变量两侧的指令优化, 然后使用cmpxchg指令比较并更新变量值(原子性)
2 AtomicReference
public class CASTest {
public static void main(String[] args) {
AtomicReference<User> userAtomicReference = new AtomicReference<>();
User tom = new User("tom",12);
User jack = new User("jack", 43);
userAtomicReference.set(tom);
// 原子引用对象比较的是对象地址
System.out.println(userAtomicReference.compareAndSet(tom, jack) + " : " + userAtomicReference.get().toString());
}
}
class User{
String name;
int age;
User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return this.name + " " + this.age;
}
}
3 CAS与自旋锁
CAS 是实现自旋锁的基础,CAS 利用 CPU 指令保证了操作的原子性,以达到锁的效果,至于自旋呢,看字面意思也很明白,自己旋转。是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,当线程发现锁被占用时,会不断循环判断锁的状态,直到获取。这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU资源。
CAS 是实现自旋锁的基础,自旋翻译成人话就是循环,一般是用一个无限循环实现。这样一来,一个无限循环中,执行一个CAS 操作,
当操作成功返回 true 时,循环结束;
当返回 false 时,接着执行循环,继续尝试 CAS 操作,直到返回 true。
// AtomicInteger.compareAndSet对应的c++源码
/**/
public final int getAndAddInt(Object o, long offset, int delta){
int v;
do {
v = getIntVolatile(o,offset);
} while(!compareAndSwapInt(o,offset,v,v + delta));
return v;
}
// 自定义自旋锁
class SpinLockDemo{
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void lock(){
Thread thread = Thread.currentThread();
while(!atomicReference.compareAndSet(null,thread)) {
// 自定义自旋 ==> 空转
}
}
public void unlock(){
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread,null);
}
}
4 CAS缺点
- 自旋操作使cpu空转,浪费CPU资源
- ABA问题:线程一从内存V中获取数据A,线程二将A修改成了B,然后又修改成了A,线程一进行CAS操作发现内存中仍是A,预期ok,线程一操作成功。
解决ABA问题:加版本号
// AtomicStampedReference
public class ABATest {
public static void main(String[] args) {
Book book1 = new Book("book1", "author1");
Book book2 = new Book("book2", "author2");
AtomicStampedReference<Book> bookAtomicStampedReference = new AtomicStampedReference<>(book1, 1); // 初始化需要版本号
System.out.println(bookAtomicStampedReference.getReference() + " " + bookAtomicStampedReference.getStamp());
bookAtomicStampedReference.compareAndSet(book1, book2, 1, 2); // 添加版本号
System.out.println(bookAtomicStampedReference.getReference() + " " + bookAtomicStampedReference.getStamp());
}
}

浙公网安备 33010602011771号