首页 > 其他分享 >JUC框架(CAS、ATOMIC、AQS)

JUC框架(CAS、ATOMIC、AQS)

时间:2024-05-27 10:30:17浏览次数:22  
标签:JUC AQS ABA CAS 版本号 线程 ATOMIC atomicStampedReference

在这里插入图片描述

文章目录


更多相关内容可查看

CAS原理

CAS(compareAndSwap)也叫比较交换,是一种无锁原子算法,其作用是让CPU将内存值更新为新值,但是有个条件,内存值必须与期望值相同,并且CAS操作无需用户态与内核态切换,直接在用户态对内存进行读写操作(意味着不会阻塞/线程上下文切换)


简单总结一下 : CAS的基本思路就是,如果这个地址上的值和期望的值相等,则给其赋予新值,否则不做任何事儿,但是要返回原值是多少。循环CAS就是在一个循环里不断的做cas操作,直到成功为止。

CAS源码示例分析

可以以下面这组代码作为示例,这组代码为Java中Random类利用AtomicLong中的方法来利用CAS来实现的

这里定义了一个 AtomicLong 类型的静态变量 seedUniquifier,并为其分配了一个初始值 8682522807148012L。AtomicLong 是 Java 并发包中的一个类,用于在高并发环境下提供线程安全的 long 类型的原子操作。

	private static final AtomicLong seedUniquifier  
    	= new AtomicLong(8682522807148012L);

    private static long seedUniquifier() {
        for (;;) {
            long current = seedUniquifier.get();
            long next = current * 181783497276652981L;
            if (seedUniquifier.compareAndSet(current, next))
                return next;
        }
    }
    
    public final boolean compareAndSet(long expect, long update) {
        return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
    }

compareAndSet 是一个原子操作,它首先比较 seedUniquifier 的当前值是否等于 current,如果是,则将其更新为 next 并返回 true。如果其他线程已经修改了 seedUniquifier 的值,则该方法会返回 false,并重新进入循环,尝试获取新的当前值并计算新值。

这种方法的一个关键优点是它可以确保在多线程环境下为每个线程提供一个唯一的种子值,而无需任何锁或其他同步机制。每次调用
seedUniquifier 方法时,它都会尝试基于当前种子值生成一个新的种子值,并使用 compareAndSet
方法确保该新值被成功地设置。

CAS的特点(ABA)

ABA问题

  • 因为CAS需要在操作值的时候,检查值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。
  • ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加1,那么A→B→A就会变成1A→2B→3A。举个通俗点的例子,你倒了一杯水放桌子上,干了点别的事,然后同事把你水喝了又给你重新倒了一杯水,你回来看水还在,拿起来就喝,如果你不管水中间被人喝过,只关心水还在,这就是ABA问题。
  • 如果你是一个讲卫生讲文明的小伙子,不但关心水在不在,还要在你离开的时候水被人动过没有,因为你是程序员,所以就想起了放了张纸在旁边,写上初始值0,别人喝水前麻烦先做个累加才能喝水。

ABA情况演示:

//        初始化为100
        AtomicReference<Integer> atomicReference=new AtomicReference<>(100);

        //线程计数
        CountDownLatch countDownLatch=new CountDownLatch(2);
        new Thread(()->{
            try {
//                等待t2获取值
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            atomicReference.compareAndSet(100,101);
            atomicReference.compareAndSet(101,100);

            countDownLatch.countDown();
        },"t1").start();

        new Thread(()->{
            int oldVal=atomicReference.get();
            try {
//                等待t1修改ABA
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            atomicReference.compareAndSet(oldVal,102);

            countDownLatch.countDown();
        },"t2").start();


        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("最终结果:"+atomicReference.get());

最后还是更新为102了

为了解决ABA,可以为其加上版本号,每次线程对原子对象做更新时,版本号也随之更新
通过匹配内存值与旧值以及匹配版本号是否相等两个条件来判断是否替换,当然这个版本号肯定大部分情况是需要唯一不相同的,时间戳正好满足这个特点
所以提供了AtomicStampedReference类,能够初始化对象的情况也携带版本号

通过AtomicStampedReference解决ABA:

AtomicStampedReference<String> atomicStampedReference=new AtomicStampedReference<>("A",1);

        CountDownLatch countDownLatch=new CountDownLatch(2);
        new Thread(()->{
            int stamp=atomicStampedReference.getStamp();
            System.out.println("第一次获取版本号:"+stamp);

            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

//            内存值对比A,版本号对比 如果一致就更新为B,且更新版本号
            atomicStampedReference.compareAndSet("A","B",atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
            System.out.println("更新后版本号:"+atomicStampedReference.getStamp());

//            B更新为A
            atomicStampedReference.compareAndSet("B","A",atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
            System.out.println("更新后版本号:"+atomicStampedReference.getStamp());

            countDownLatch.countDown();
        },"t1").start();

        new Thread(()->{
            int stamp=atomicStampedReference.getStamp();
            String oldVal=atomicStampedReference.getReference();
            System.out.println("第一次获取版本号:"+stamp);

            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

//            通过已经获取到的旧值和版本号做更新
            atomicStampedReference.compareAndSet(oldVal,"X",stamp,stamp+1);


            countDownLatch.countDown();
        },"t2").start();


        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("最终结果:"+atomicStampedReference.getReference());

循环时间长开销大

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

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

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

Jdk中CAS运用

整个AQS同步组件、Atomic原子类操作等等都是以CAS实现的,甚至ConcurrentHashMap在1.8的版本中也调整为了CAS+Synchronized。可以说CAS是整个JUC的基石。

ATOMIC

java.util.concurrent.atomic存放原子操作的类,这些类中提供了一些原子性操作的方法, 通过这些原子类可以避免线程安全问题的发生

虽然涉及到的类很多,但是原理和AtomicInteger都是一样,使用CAS进行的原子操作,其方法和使用都是大同小异的,这里就不做挨个分析了。

AQS

AQS简介

AQS 的全称为 AbstractQueuedSynchronizer ,翻译过来的意思就是抽象队列同步器。这个类在 java.util.concurrent.locks 包下面。
AQS 为构建锁和同步器提供了一些通用功能的实现,因此,使用 AQS 能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的 ReentrantLockSemaphore,其他的诸如 ReentrantReadWriteLockSynchronousQueueFutureTask(jdk1.7) 等等皆是基于 AQS 的。

AQS原理

  • 当线程请求锁时,如果锁已被其他线程持有,则当前线程会被封装成一个Node节点并加入到等待队列的尾部。
  • 当持有锁的线程释放锁时,AQS会从队列头部开始,检查节点是否处于等待状态,并唤醒该节点对应的线程来获取锁。
  • 被唤醒的线程会尝试获取锁,如果成功则继续执行后续任务,否则继续进入等待队列等待被唤醒。


CLH 队列锁

  • AQS内部维护了一个双向队列(FIFO),用于管理请求锁的线程。
  • 当有线程请求锁时,AQS会将其封装成一个Node节点,并加入到等待队列中,线程则会进入阻塞状态。
  • 当持有锁的线程释放锁时,AQS会从等待队列中唤醒一个线程来获取锁,从而实现线程的同步和互斥。

AQS 定义两种资源共享方式 :
Exclusive(独占)
只有一个线程能执行,如 ReentrantLock。又可分为公平锁和非公平锁,ReentrantLock 同时支持两种锁,下面以 ReentrantLock 对这两种锁的定义做介绍:

  • 公平锁 :按照线程在队列中的排队顺序,先到者先拿到锁
  • 非公平锁 :当线程要获取锁时,先通过两次 CAS 操作去抢锁,如果没抢到,当前线程再加入到队列中等待唤醒。

Share(共享):
多个线程可同时执行,如 Semaphore/CountDownLatchSemaphoreCountDownLatchCyclicBarrierReadWriteLock
ReentrantReadWriteLock 可以看成是组合式,因为 ReentrantReadWriteLock 也就是读写锁允许多个线程同时对某一资源进行读

标签:JUC,AQS,ABA,CAS,版本号,线程,ATOMIC,atomicStampedReference
From: https://blog.csdn.net/Aaaaaaatwl/article/details/139219397

相关文章

  • 深入浅出-CAS算法原理
    1、什么是CAS?CAS:CompareandSwap,即比较再交换。jdk5增加了并发包java.util.concurrent.*,其下面的类使用CAS算法实现了区别于synchronouse同步锁的一种乐观锁。JDK5之前Java语言是靠synchronized关键字保证同步的,这是一种独占锁,也是是悲观锁。2、CAS算法理解对CAS的理......
  • CAS架构与原理简介
    1.会话与CookieHTTP是无状态协议,客户端与服务端之间的每次通信都是独立的,而会话机制可以让服务端鉴别每次通讯过程中的客户端是否是同一个,从而保证业务的关联性。Session是服务器使用一种类似于散列表的结构,用来保存用户会话所需要的信息.Cookie作为浏览器缓存,存储SessionI......
  • CAS单点登录原理解析(转载)
       1、基于Cookie的单点登录的回顾    基于Cookie的单点登录核心原理:   将用户名密码加密之后存于Cookie中,之后访问网站时在过滤器(filter)中校验用户权限,如果没有权限则从Cookie中取出用户名密码进行登录,让用户从某种意义上觉得只登录了一次。   该方......
  • P1474 [USACO2.3] Money System / [USACO07OCT]Cow Cash G
    有点水,但是细究还是有点意思的题目https://www.luogu.com.cn/problem/P1474一开始的代码:#define_CRT_SECURE_NO_WARNINGS#include<iostream>#include<vector>#include<algorithm>#include<math.h>#include<sstream>#include<string>#include<......
  • Ncast高清智能录播系统存在任意文件读取漏洞以及busiFacade RCE漏洞
    漏洞描述由于系统对用户输入的文件路径未进行充分验证,允许用户输入任意文件路径,导致存在任意文件读取漏洞,攻击者可以利用该漏洞获取系统配置文件、密码文件等。该系统的/classes/common/busiFacade.php接口存在RCE漏洞,攻击者可以利用此漏洞执行任意命令。fofaapp="Ncast-产品"......
  • 探索Go语言的原子操作秘籍:sync/atomic.Value全解析
    引言​在并发编程的世界里,数据的一致性和线程安全是永恒的话题。Go语言以其独特的并发模型——goroutine和channel,简化了并发编程的复杂性。然而,在某些场景下,我们仍然需要一种机制来保证操作的原子性。这就是sync/atomic.Value发挥作用的地方。原子性:并发编程的基石​......
  • Java JUC&多线程 基础完整版
    JavaJUC&多线程基础完整版目录JavaJUC&多线程基础完整版1、多线程的第一种启动方式之继承Thread类2、多线程的第二种启动方式之实现Runnable接口3、多线程的第三种实现方式之实现Callable接口4、多线的常用成员方法5、线程的优先级6、守护线程7、线程的让出8、线程插队9、同......
  • 蓝牙|软件 QCC51xx系列开发之LE AUDIO Auracast使用指导
    本文适用范围:   ADK23.3~file=app1##############USERKEYS###############Thesekeysareusedtoconfigurethesoftwareortosavesettings.#Seethecorrespondingadk\src\domains\common\ps_key_map.h#USR1-Thiskeyisreserved.Itisusedto......
  • (转)Go 每日一库之 cast
    原文:https://juejin.cn/post/6844904056750620679简介今天我们再来介绍spf13大神的另一个库cast。cast是一个小巧、实用的类型转换库,用于将一个类型转为另一个类型。最初开发cast是用在hugo中的。快速使用先安装: 复制代码$gogetgithub.com/spf13/cast后使用......
  • Shell阶段04 shell流程之case语句, 服务启动停止脚本(rsync, nginx), shell加锁机制
    1.流程控制语句之case语句case主要作用是对程序的选择,循环等操作#语法:case变量in变量值1)命令序列;;#命令序列结束符变量值2)命令序列;;变量值3)命令序列;;变量值N)命令序列......