Java中CAS原理详解

目录

一、CAS简介

二、悲观锁和乐观锁

三、unsafe类

四、自旋锁问题

五、ABA问题

六、只能保证一个共享变量的原子操作


一、CAS简介

CAS是乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。

CAS(Compare And Swap)意为比较并且交换,CAS它是一个原子操作。CAS操作涉及到三个值:当前内存中的值V,逾期内存中的值E和待更新的值U。如果当前内存中的值V等于预期值E,则将内存中的值更新为U,CAS操作成功。否则不更新CAS操作失败。

二、悲观锁和乐观锁

悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中的synchronized锁和ReentrantLock都是悲观的锁。

乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

三、unsafe类

Java和C++语言的一个重要区别就是Java中我们无法直接操作一块内存区域,不能像C++中那样可以自己申请内存和释放内存。Java中的Unsafe类为我们提供了类似C++手动管理内存的能力。
Unsafe类,全限定名是sun.misc.Unsafe,从名字中我们可以看出来这个类对普通程序员来说是“危险”的,一般应用开发者不会用到这个类。

Unsafe提供的CAS操作:

CAS在Java中有很多应用,JUC以及java.util.concurrent.atomic下面的原子类都用到了CAS。

Unsafe为Java提供了很多底层功能,其中Java中的CAS功能就是通过这个类来实现的。

public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

Unsafe中提供了Object,int和long类型的CAS操作。其他类型的需要自己实现。CAS它是一个原子操作。要保证线程安全性,除了原子性,还有可见性和有序性。可见性和有序性在Java中都可以通过volatile来实现。

Java中的原子类(java.util.concurrent.atomic

java.util.concurrent.atomic包中的类通过volatile+CAS重试保证线程安全性。
java.util.concurrent.atomic包下面的原子类可以分为四种类型:

  1. 原子标量:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
  2. 数组类:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
  3. 更新类:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
  4. 复合变量类:AtomicMarkableReference,AtomicStampedReference

我们主要分析一下AtomicInteger这个类如何保证线程安全性:

AtomicInteger的value是volatile的

public class AtomicInteger extends Number implements java.io.Seriablizable {
    ...
    private volatile int value; // value是volatile的,保证了可见性和有序性
    ...
}

AtomicInteger中的value是volatile的,volatile可以保证可见性和有序性。

public final int get() {
    return value;
}

可以看到AtomicInteger的get操作是不加锁的,对于非volatile类型的共享变量,并发操作时,一个读线程未必能立马读取到其他线程对这个共享变量的修改。但是这里的value是volatile的,因此可以立马看到其他线程对value的修改。

incrementAndGet操作

public final int incrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

incrementAndGet操作会先将value加1,然后返回新的值。这个方法内部会调用Unsafe的getAndAddInt方法:

public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); // CAS原子更新+循环重试

    return var5;
}

可以看到Unsafe中在循环体内先读取内存中的value值,然后CAS更新,如果CAS更新成功则退出,如果更新失败,则循环重试直到更新成功。

在前面的文章中(Java中的Unsafe)我们分析了Unsafe中提供了三种类型对象的CAS操作:Object,int和long类型。AtomicLong是通过Unsafe提供的long类型的CAS操作实现的,AtomicReference是通过Unsafe提供的Object类型的CAS操作实现的。

四、自旋锁问题

自旋锁+CAS如果长时间不成功,会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。

java.util.concurrent.atomic通过基于CAS的乐观锁保证线程安全性。在多读少写的场景下,较synchronized锁和ReentrantLock的悲观锁性能会更好。

五、ABA问题

因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。AtomicStampedReference类具有版本号功能。

六、只能保证一个共享变量的原子操作

当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。

 

 

  • 2
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值