多线程CAS机制(图解)

导读:本篇文章讲解 多线程CAS机制(图解),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com


前言

博主个人社区:开发与算法学习社区

博主个人主页:Killing Vibe的博客

欢迎大家加入,一起交流学习~~

上篇总结了以下多线程场景下常见锁的策略,这篇总结一下CAS机制引起的ABA问题,以及解决方式。

一、CAS是什么?

乐观锁的一种实现,程序并不会阻塞,只会不断重试。

CAS: 全称Compare and swap,字面意思:”比较并交换“,一个 CAS 涉及到以下操作:
我们假设内存中的原数据V,旧的预期值A,需要修改的新值B。

  1. 比较 A 与 V 是否相等。(比较)
  2. 如果比较相等,将 B 写入 V。(交换)
  3. 返回操作是否成功。

二、CAS如何实现的?

针对不同的操作系统,JVM 用到了不同的 CAS 实现原理,简单来讲:

  • java 的 CAS 利用的的是 unsafe 这个类提供的 CAS 操作;
  • unsafe 的 CAS 依赖了的是 jvm 针对不同的操作系统实现的 Atomic::cmpxchg;
  • Atomic::cmpxchg 的实现使用了汇编的 CAS 操作,并使用 cpu 硬件提供的 lock 机制保证其原子
    性。
    简而言之,是因为硬件予以了支持,软件层面才能做到

三、CAS的应用

3.1 原子类

标准库提供了 java.util.concurrent 包(juc包,并发工具包,包含原子类,线程安全集合比如ConcurrentHashMap,CopyOnWriteArrayList等等)

举个栗子:

比如定义一个整型变量int i = 0:

i++ 或者 i – 这些都是非原子性的操作,多线程并发会有线程安全问题。这个时候就要使用原子类保证它的线程安全性。

那什么是原子性呢?

就是指该操作对应CPU的一条指令,这个操作不会被中断,要么全部执行,要么全不执行,不会存在中间状态,那么这种操作就是一个原子性操作。

比如int a = 10 这种就是直接将常量10赋值给a变量,是一个原子性操作,要么赋值成功要么失败。

再比如a += 10 这种就是非原子性的操作,先要读取当前a变量的值,然后到寄存器中进行a+10计算,最后将计算得出的值重新赋值给a变量(对应了三个原子性操作)

所以我们可以使用原子类来保证线程安全性,比如常见的AtomicInteger类:

//有参构造可以传入参数,传入多少就从多少开始计数,无参构造默认为0.
AtomicInteger atomicInteger = new AtomicInteger(0); 
// 相当于 i++
atomicInteger.getAndIncrement();

看下源码:
在这里插入图片描述

通过形如上述代码就可以实现一个原子类. 不需要使用重量级锁, 就可以高效的完成多线程的自增操作.

3.2 自旋锁

使用CAS来实现自旋锁,乐观锁的一种实现

自旋锁指的是在获取锁失败的线程不进入阻塞态,而是在CPU上空转(线程不让出CPU,而是跑一些无用指令),不断查询当前锁的状态。

伪代码:

public class SpinLock {
    private Thread owner = null;
    public void lock(){
        // 通过 CAS 看当前锁是否被某个线程持有. 
        // 如果这个锁已经被别的线程持有, 那么就自旋等待. 
        // 如果这个锁没有被别的线程持有, 那么就把 owner 设为当前尝试加锁的线程. 
        while(!CAS(this.owner, null, Thread.currentThread())){
       }
   }
    public void unlock (){
        this.owner = null;
   }
}

解释一下CAS(this.owner, null, Thread.currentThread())操作:

只有当this.owner == null , 即当前自旋锁没有被任何线程持有 =>

就尝试将this.owner ==Thread.currentThread() ,将持有锁的线程设置为当前线程

四、CAS引发的ABA问题

4.1 什么是ABA问题

有两个线程t1和t2,同时修改共享变量num,初始num == A

正常情况下,只有一个线程会将num 改为正确值,另一个线程在修改时num != A,另一个线程的工作内存的值已经过期了,因此无法修改

但如果线程 t2 经过多次修改后又回到了同一个值,此时线程 t1 会以为线程 t2 没被修改过,然后就去修改值 . 此时t2之间做的各种修改 其实对 t1来说都是不可见的 。

下面是ABA问题的图解:

在这里插入图片描述

4.2 如何解决

为了解决ABA问题,就要引入版本号机制。

给要修改的值, 引入版本号. 在 CAS 比较数据当前值和旧值的同时, 也要比较版本号是否符合预期.

  • CAS 操作在读取旧值的同时, 也要读取版本号.
  • 真正修改的时候,
    如果当前版本号和读到的版本号相同, 则修改数据, 并把版本号 + 1.
    如果当前版本号高于读到的版本号. 就操作失败(认为数据已经被修改过了).

详情可以看下面这个链接:

版本号机制

总结

以上就是多线程CAS机制的详解了,后续博主会更新Synchronized 原理 和JUC的常见类及用法,欢迎关注,点赞+收藏~~~

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由半码博客整理,本文链接:https://www.bmabk.com/index.php/post/115463.html

(0)

相关推荐

发表回复

登录后才能评论
半码博客——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!