多线程—CAS(五)

一、CAS是什么

全称Compare-And-Swap,即“比较并交换”,相当于一个原子的操作,同时完成“读取内存,比较是否相等,修改内存”这三个步骤,本质上需要cpu指令的支持

判断内存某个位置的值是否为预期值。若是则更新为新值,实现比较与交换;若不是则无事发生

CAS伪代码:

boolean CAS(address, expectValue, swapValue) {
//如果内存位置的值与预期原值相同,就把原值用新值替换,并返回true
 if (&address == expectedValue) {
   &address = swapValue;
        return true;
   }
//否则无事发生,返回false
    return false;
}

可以使用CAS完成一些操作,进一步替代“加锁”,这也给编写线程安全的代码引入了新思路

基于CAS实现线程安全的方式,也称为“无锁编程”

        优点:保证线程安全,同时避免阻塞(效率)

        缺点:代码会更复杂,不好理解;只能够适合一些特定场景,不如加锁更普适 

当多个线程同时对某个资源进行CAS操作,只能有一个线程操作成功,但是并不会阻塞其他线程,其他线程只会收到操作失败的信号

二、CAS实现原子类

在标准库中提供的 java.util.concurrent.atomic 包里面有原子类,原子类内部用的是CAS,所以性能要比加锁实现i++高很多,原子类有以下几个

  • AtomicBoolean
  • AtomicInteger
  • AtomicInntegerArry
  • AtomicLong
  • AtomicReference
  • AtomicStampedReference

来看一下经典案例:

public class demo12 {
    public static int count=0;
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(()->{
            for (int i = 0; i < 5000; i++) {
                count++;
            }
        });
        Thread t2=new Thread(()->{
            for (int i = 0; i < 5000; i++) {
                count++;
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("count="+count);
    }
}

 

利用AtomicInteger类对int进行包装,此时进行自加(基于CAS指令实现)就是原子的了

import java.util.concurrent.atomic.AtomicInteger;

public class demo12 {
    public static AtomicInteger count=new AtomicInteger(0);
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(()->{
            for (int i = 0; i < 5000; i++) {
                count.getAndIncrement();//count++
                /*
                 count.getAndDecrement();  count--
                 count.incrementAndGet();  ++count
                 count.decrementAndGet();  --count
                 count.getAndDecrement(2); count-=2
                 */
            }
        });
        Thread t2=new Thread(()->{
            for (int i = 0; i < 5000; i++) {
                count.getAndIncrement();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count.get());
    }
}

来看下这里的自加操作是如何实现的

其中又调用了unsafe类中的getAndAddInt()方法

第362行调用unsafe类内的cas方法,jvm会实现cas汇编指令

若再继续点进compareAndSwapInt()方法,就会发现接下来是native本地方法,也就是jvm源码中,使用C++实现的逻辑

 可以说,CAS本质上是cpu提供的指令,被操作系统封装提供成api,再被jvm封装也提供成api供程序员使用 

以上不太好理解,我们直接使用伪代码来理解下AtomicInteger类如何进行自加操作

class AtomicInteger {
    private int value;
    public int getAndIncrement() {
        int oldValue = value;
        while ( CAS(value, oldValue, oldValue+1) != true) {
            oldValue = value;
       }
        return oldValue;
   }
}

来多线程穿插执行下看下是如何利用CAS实现原子类的:

上述过程没有加锁,但是实现了线程安全 

前面我们遇到的“线程不安全”本质上是进行自增操作的过程中,线程穿插执行了

CAS这里也是让CAS不要穿插执行,核心思路与加锁相似

不过,加锁是通过阻塞的方式避免穿插执行;CAS则是通过重试的方式避免穿插执行 

这是非常妙的方法,但是这是需要一定的硬件支持的!

三、CAS实现自旋锁

伪代码如下: 

public class SpinLock {
    private Thread owner = null;//记录当前锁被哪个线程获取到了,若为null即未加锁状态
    public void lock(){ 
        //cas(此锁拥有者,null,调用lock方法的线程的引用)
        /*
          通过 CAS 看当前锁是否被某个线程持有. 
          如果这个锁已经被别的线程持有, 那么就自旋等待. 
          如果这个锁没有被别的线程持有, 那么就把 owner 设为当前尝试加锁的线程.
        */
        while(!CAS(this.owner, null, Thread.currentThread())){
       }
   }

    //释放锁,即把锁拥有设为null
    public void unlock (){
        this.owner = null;
   }
}

四、CAS的ABA问题

CAS进行操作的关键,是通过重试 值“没有发生变化”来作为“没有其他 线程穿插执行”的判定依据 

就是当判定相等时,不确定value是一直没有更改,还是被别的线程更改却又被另外线程改回来了

比如买手机,买到的是一个翻新机,这就是又把二手的又翻新回来出售,我个人是无法确定的

看如下场景,通过重试value值有无发生变化来判断有无穿插执行,这里完全没问题,发现被穿插执行,最终只扣款一次

但若在t2扣款完成后而t1又没有进行CAS时,t3又转入一笔钱500

(把value又改回1000再执行t1的CAS(1000,1000,500))

这样t1就又可以成功进行一次扣款,也就是说这仨线程一台戏一共扣了1000!

 这就实现了把值A->B->A,最后导致bug!

只要让判定的数值,按照一个 方向增长即可(不要反复横跳)

可以引入版本号version,约定每次修改余额version++,此时在使用CAS判定时,就不是直接 判定数值了,而是判定版本号,看版本号是否变化:若版本号不变,则表示没有其他线程穿插执行 

总的来说,解决ABA问题就是:

        给要修改的数据引入版本号,在CAS比较数据当前值和旧值的同时,也要比较版本号是否符合预期。若发现当前版本号和之前读到的版本号一致,就真正 执行修改操作,并让版本号自增;若发现当前版本号比 之前读到的版本号大,就认为操作失败

在实际开发中,并不会直接使用CAS,用的都是封装好的

  • 27
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值