天增的博客
首页
博客
  • 分布式解决方案
  • Java并发工具包
  • redis
  • LeetCode
  • 系统设计
  • JVM体系
Github (opens new window)
Rss (opens new window)
  • zh-CN
  • en-US
首页
博客
  • 分布式解决方案
  • Java并发工具包
  • redis
  • LeetCode
  • 系统设计
  • JVM体系
Github (opens new window)
Rss (opens new window)
  • zh-CN
  • en-US
  • Java并发工具包
  • 并发基础
    • 线程基础
      • Thread的状态
      • 进程与线程
      • 正确停止线程的方式
      • Thread的实现方式
      • waitnotifynotifyAll
      • 生产者消费者模型
    • 线程安全
      • 线程不安全
      • 线程安全
      • 需要注意线程安全问题的情况
  • 并发工具
    • 线程协作
      • Semaphore信号量
      • CountDownLatch详解
      • 使用CompletableFuture解决旅游平台问题
      • 使用CyclicBarrier解决团建问题
    • Future
      • Future主要功能
      • FutureTask源码分析
    • ThreadLocal
      • ThreadLocal内存泄漏
      • ThreadLocal使用场景
    • 原子类
      • 原子类的作用概览
      • 原子类的性能分析
    • 阻塞队列
      • 常见的阻塞队列
      • 阻塞队列的常用方法
      • 什么是阻塞队列
    • 并发容器
      • HashMap
      • CopyOnWriteArrayList
      • ConcurrentHashMap详解
    • 线程池
      • 为什么多线程会带来性能问题
      • 线程池的优势
      • 创建线程池的参数
        • 如何设置线程数
      • 线程池线程复用原理
      • ForkJoin框架
    • 各种锁
      • 锁的种类和特点
        • 公平锁非公平锁
        • 自旋锁非自旋锁
        • 共享锁独占锁
        • 乐观锁和悲观锁
      • JVM锁优化
      • synchronized和Lock的对比
      • lock的常用方法
  • 底层原理
    • CAS原理
      • CAS介绍
      • CAS的思路
      • 使用示例
      • CAS的问题
    • AQS框架
    • 伪共享
    • java内存模型
      • Java内存模型介绍
      • happens-before规则
  • topic
  • Java并发工具包
  • 底层原理
  • CAS原理
2022-04-21
目录

CAS原理

# CAS原理

# CAS介绍

Cas的全拼是 Compare and Swap ,翻译过来是比较和交换,这是一种思想、一种算法,也被称之为无锁更新。

Cas是一种非常常见的算法,也是面试中的常客,同时也是原子类和乐观锁的底层原理。

在多线程的情况下,访问一个共享资源是线程不安全的,通常我们会使用互斥锁,即一个时刻只能有一个线程访问共享资源。但是,互斥锁由于需要CPU切换上下文线程,进而导致性能低下。

而CAS另辟蹊径,没有通过互斥锁,而是当多个线程同时使用CAS算法去更新共享资源时,只会有一个线程能够成功更新,而其他线程都会更新失败,不过与互斥锁不同的是,更新失败的线程并不会阻塞,而是告知此次操作竞争失败,但是还可以再次尝试。

就比如,sql中的条件更新一样:update set id=3 from table where id=2。因为单条sql执行具有原子性,如果有多个线程同时执行此sql语句,只有一条能更新成功。

# CAS的思路

在大多数的处理器中,都会实现CAS相关的指令,这一条指令就能够实现比较和交换的操作。

也正是由于这一条CPU指令,所以CAS相关的操作是具备原子性的,这个操作再执行期间不会被打算,这样能够保证并发的安全性。而这个原子性是由CPU进行提供,在使用时无需程序猿来操心。

CAS有三个操作数,内存值V,预期值A,要修改的值B。

处理逻辑是,当预期值和内存值相同时,才会将内存值修改为B。在执行CAS的时候,如果内存中的V正好等于传入的A,则会把V的值修改成B;如果内存中的V和传入的A不相等,则说明A的值已经被其他线程修改过,那么本次的CAS修改失败。

JDK中大量使用了CAS来更新数据而防止加锁(synchronized 重量级锁)来保持原子更新。

# 使用示例

如果不使用CAS,在高并发下,多线程同时修改一个变量的值我们需要synchronized加锁(可能有人说可以用Lock加锁,Lock底层的AQS也是基于CAS进行获取锁的)。

public class Test {
    private int i=0;
    public synchronized int add(){
        return i++;
    }
}
    

java中为我们提供了AtomicInteger 原子类(底层基于CAS进行更新数据的),不需要加锁就在多线程并发场景下实现数据的一致性。

public class Test {
    private  AtomicInteger i = new AtomicInteger(0);
    public int add(){
        return i.addAndGet(1);
    }
}    

# CAS的问题

# ABA问题

有一种情况,原本的值是A变成了B然后又变成了A,CAS在检查这种情况的时候,会认为值没有发送变化,实际上是已经发生了变化。

ABA解决的方案就是每次更新就加上版本号,1A->2B-3A。

从Java 1.5开始,JDK的Atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法的作用是首先检查当前引用是否等于预期引用,并且检查当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

# 循环时间长开销大

自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。

如果JVM能支持处理器提供的pause指令,那么效率会有一定的提升。

pause指令有两个作用:

  • 第一,它可以延迟流水线执行命令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零;
  • 第二,它可以避免在退出循环的时候因内存顺序冲突(Memory Order Violation)而引起CPU流水线被清空(CPU Pipeline Flush),从而提高CPU的执行效率。

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

当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁。

还有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如,有两个共享变量i = 2,j = a,合并一下ij = 2a,然后用CAS来操作ij。

从Java 1.5开始,JDK提供了AtomicReference类来保证引用对象之间的原子性,就可以把多个变量放在一个对象里来进行CAS操作。

最近更新
01
以 root 身份启动 transmission-daemon
12-13
02
Debian系统安装qbittorrent-nox
12-09
03
LXC Debain12安装zerotier并实现局域网自动nat转发
07-29
更多文章>
Theme by Vdoing | Copyright © 2015-2024 天增 | 苏ICP备16037388号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式