首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >多线程CAS机制(图解)

多线程CAS机制(图解)

作者头像
VIBE
发布2022-11-18 14:09:08
发布2022-11-18 14:09:08
8890
举报
文章被收录于专栏:算法与开发算法与开发

文章目录


前言

博主个人社区:开发与算法学习社区 博主个人主页: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类:

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

看下源码:

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

3.2 自旋锁

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

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

伪代码:

代码语言:javascript
复制
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的常见类及用法

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-11-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • 前言
  • 一、CAS是什么?
  • 二、CAS如何实现的?
  • 三、CAS的应用
    • 3.1 原子类
    • 3.2 自旋锁
  • 四、CAS引发的ABA问题
    • 4.1 什么是ABA问题
    • 4.2 如何解决
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档