JUC-无锁之CAS

news2024/9/22 19:36:38

问题提出 (应用之互斥)

package cn.itcast;
import java.util.ArrayList;
import java.util.List;
interface Account {
    // 获取余额
    Integer getBalance();
    // 取款
    void withdraw(Integer amount);
    /**
 * 方法内会启动 1000 个线程,每个线程做 -10 元 的操作
 * 如果初始余额为 10000 那么正确的结果应当是 0
 */
    static void demo(Account account) {
        List<Thread> ts = new ArrayList<>();
        long start = System.nanoTime();
        for (int i = 0; i < 1000; i++) {
            ts.add(new Thread(() -> {
                account.withdraw(10);
            }));
        }
        ts.forEach(Thread::start);
        ts.forEach(t -> {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        long end = System.nanoTime();
        System.out.println(account.getBalance() 
                           + " cost: " + (end-start)/1000_000 + " ms");
    }
}

原有实现并不是线程安全的

class AccountUnsafe implements Account {
    private Integer balance;
    public AccountUnsafe(Integer balance) {
        this.balance = balance;
    }
    @Override
    public Integer getBalance() {
        return balance;
    }
    @Override
    public void withdraw(Integer amount) {
        balance -= amount;
    }
}

withdraw 方法

public void withdraw(Integer amount) {
    balance -= amount;
}

对应的字节码

ALOAD 0 // <- this
ALOAD 0
GETFIELD cn/itcast/AccountUnsafe.balance : Ljava/lang/Integer; // <- this.balance
INVOKEVIRTUAL java/lang/Integer.intValue ()I // 拆箱
ALOAD 1 // <- amount
INVOKEVIRTUAL java/lang/Integer.intValue ()I // 拆箱
ISUB // 减法
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer; // 结果装箱
PUTFIELD cn/itcast/AccountUnsafe.balance : Ljava/lang/Integer; // -> this.balance

多线程执行

ALOAD 0 // thread-0 <- this 
ALOAD 0 
GETFIELD cn/itcast/AccountUnsafe.balance // thread-0 <- this.balance 
INVOKEVIRTUAL java/lang/Integer.intValue // thread-0 拆箱
ALOAD 1 // thread-0 <- amount 
INVOKEVIRTUAL java/lang/Integer.intValue // thread-0 拆箱
    ISUB // thread-0 减法
INVOKESTATIC java/lang/Integer.valueOf // thread-0 结果装箱
PUTFIELD cn/itcast/AccountUnsafe.balance // thread-0 -> this.balance 
 
 
ALOAD 0 // thread-1 <- this 
ALOAD 0 
GETFIELD cn/itcast/AccountUnsafe.balance // thread-1 <- this.balance 
INVOKEVIRTUAL java/lang/Integer.intValue // thread-1 拆箱
ALOAD 1 // thread-1 <- amount 
INVOKEVIRTUAL java/lang/Integer.intValue // thread-1 拆箱
ISUB // thread-1 减法
INVOKESTATIC java/lang/Integer.valueOf // thread-1 结果装箱
PUTFIELD cn/itcast/AccountUnsafe.balance // thread-1 -> this.balance

原因:Integer虽然是不可变类,其方法是线程安全的,但是以上操作涉及到了多个方法的组合,等价于以下代码:

balance = new Integer(Integer.valueOf(balance) - amount);

前一个方法(valueOf)的结果决定后一个方法(构造方法),这种组合在多线程环境下线程不安全。

解决思路-锁(悲观互斥)

首先想到的是给 Account 对象加锁

class AccountUnsafe implements Account {
    private Integer balance;
    public AccountUnsafe(Integer balance) {
        this.balance = balance;
    }
    @Override
    public synchronized Integer getBalance() {
        return balance;
    }
    @Override
    public synchronized void withdraw(Integer amount) {
        balance -= amount;
    }
}

 解决思路-无锁(乐观重试)

class AccountSafe implements Account {
    private AtomicInteger balance;
    public AccountSafe(Integer balance) {
        this.balance = new AtomicInteger(balance);
    }
    @Override
    public Integer getBalance() {
        return balance.get();
    }
    @Override
    public void withdraw(Integer amount) {
        while (true) {
            int prev = balance.get();
            int next = prev - amount;
            if (balance.compareAndSet(prev, next)) {
                break;
            }
        }
        // 可以简化为下面的方法
        // balance.addAndGet(-1 * amount);
    }

CAS 与 volatile

CAS

前面看到的 AtomicInteger 的解决方法,内部并没有用锁来保护共享变量的线程安全。那么它是如何实现的呢?

compareAndSet 正是做这个检查,在 set 前,先比较 prev 与当前值不一致了,next 作废,返回 false 表示失败。比如别的线程已经做了减法,当前值已经被减成了 990那么本线程的这次 990 就作废了,进入 while 下次循环重试, 检查是否一致,以 next 设置为新值,返回 true 表示成功。

其中的关键是 compareAndSet,它的简称就是 CAS (也有 Compare And Swap 的说法),它必须是原子操作。  

注意:

其实 CAS 的底层是 lock cmpxchg 指令(X86 架构),在单核 CPU 和多核 CPU 下都能够保证【比较-交 换】的原子性。

在多核状态下,某个核执行到带 lock 的指令时,CPU 会让总线锁住,当这个核把此指令执行完毕,再 开启总线。这个过程中不会被线程的调度机制所打断,保证了多个线程对内存操作的准确性,是原子 的

volatile

获取共享变量时,为了保证该变量的可见性,需要使用 volatile 修饰,CAS 必须借助 volatile 才能读取到共享变量的最新值来实现【比较并交换】的效果。

它可以用来修饰成员变量和静态成员变量,他可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取 它的值,线程操作 volatile 变量都是直接操作主存。即一个线程对 volatile 变量的修改,对另一个线程可见。volatile 仅仅保证了共享变量的可见性,并不能保证原子性

 为什么无锁效率高

无锁效率高的原因本质就是避免了线程的上下文切换导致的耗时。

  • 无锁情况下,即使重试失败,线程始终在高速运行,没有停歇,类似于自旋。而 synchronized 会让线程在没有获得锁的时候,发生上下文切换,进入阻塞。线程的上下文切换是费时的,在重试次数不是太多时,无锁的效率高于有锁,类似于自旋等待了。

  • 线程就好像高速跑道上的赛车,高速运行时,速度超快,一旦发生上下文切换,就好比赛车要减速、熄火, 等被唤醒又得重新打火、启动、加速... 恢复到高速运行,代价比较大

  • 但无锁情况下,因为线程要保持运行,需要额外 CPU 的支持,CPU 在这里就好比高速跑道,没有额外的跑 道,线程想高速运行也无从谈起,虽然不会进入阻塞,但由于没有分到时间片,仍然会进入可运行状态,还 是会导致上下文切换。所以总的来说,当线程数小于等于cpu核心数时,使用无锁方案是很合适的,因为有足够多的cpu让线程运行。当线程数远多于cpu核心数时,无锁效率相比于有锁就没有太大优势,因为依旧会发生上下文切换。

CAS 的特点

结合 CAS 和 volatile 可以实现无锁并发,适用于线程数少、多核 CPU 的场景下。

  • CAS 是基于乐观锁的思想:最乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系,我吃亏点再重试呗。

  • synchronized 是基于悲观锁的思想:最悲观的估计,得防着其它线程来修改共享变量,我上了锁你们都别想改,我改完了解开锁,你们才有机会。

  • CAS 体现的是无锁并发、无阻塞并发,请仔细体会这两句话的意思

    • 因为没有使用 synchronized,所以线程不会陷入阻塞,这是效率提升的因素之一

    • 但如果竞争激烈即并发量过高,可以想到重试必然频繁发生,线程也无可避免地会进行上下文切换,反而效率会受影响。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2105876.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2024全国大学省数学建模竞赛C题-优秀论文分析(2023)

​某商超蔬菜类商品动态定价与补货决策研究 摘 要 随着生鲜市场规模的持续扩大&#xff0c;蔬菜零售行业的竞争也愈加激烈。为帮助某商超 改善经营模式&#xff0c;本文基于题目所给数据信息&#xff0c;建立数学模型进行分析&#xff0c;从而制定合理 的蔬菜类商品动态定价与…

ARM发布新一代高性能处理器N3

简介 就在2月21日&#xff0c;ARM发布了新一代面向服务器的高性能处理器N3和V3&#xff0c;N系列平衡性能和功耗&#xff0c;而V系列则注重更高的性能。此次发布的N3&#xff0c;单个die最高32核&#xff08;并加入到CCS&#xff0c;Compute Subsystems&#xff0c;包含Core&a…

C语言函数不同个数、大小形参对执行速度的影响:以Cortex-M3为例从汇编角度分析原因

0 资料&工具 Cortex M3权威指南(中文).pdf keil5&#xff08;用于仿真查看汇编代码、栈变化&#xff09;1 C语言函数不同个数、大小形参对执行速度的影响&#xff1a;以Cortex-M3为例从汇编角度分析原因 C语言中有条不成文的规定&#xff1a;不建议函数的形参数量超过4个…

C8T6超绝模块--LED

C8T6超绝模块–LED 大纲 怎样点亮LED结构体分析代码流程 具体案例 怎样点亮LED 首先不同的芯片的接法不一样&#xff0c;需要自己查看自己的芯片的原理图&#xff0c;我使用的是C8T6&#xff0c;使用的PC13接入的LED 注意看&#xff1a;怎么才能使LED灯亮呢&#xff1f; …

flux 文生图大模型 自有数据集 lora微调训练案例

参考: https://github.com/ostris/ai-toolkit 目前 Flux 出现了 3 个训练工具 SimpleTuner https://github.com/bghira/SimpleTuner X-LABS 的https://github.com/XLabs-AI/x-flux ai-toolkit https://github.com/ostris/ai-toolkit 待支持:https://github.com/kohya-ss/sd-s…

RK3588平台开发系列讲解(显示篇)MIPI详解

文章目录 一、DSI和CSI二、初识MIPI2.1、框架2.2、参数2.3、接口三、设备树下CSI的配置沉淀、分享、成长,让自己和他人都能有所收获!😄 一、DSI和CSI DSI( Display Serial Interface ) :位于处理器和显示模组之间的显示串行接口CSI( Camera Serial Interface ) : 位于…

Linux 安装nodejs环境

文章目录 Node.js简介Node.js的核心特性Node.js的生态系统Node.js的模块系统 部署下载Node.js预编译二进制包上传到Linux服务器并解压配置环境变量验证安装 部署在下边&#xff0c;我先对nodejs进行一些介绍&#xff0c;大家了解一下 Node.js简介 Node.js是一个基于Chrome V8…

计算机毕业设计Spark+PyTorch知识图谱房源推荐系统 房价预测系统 房源数据分析 房源可视化 房源大数据大屏 大数据毕业设计 机器学习

《SparkPyTorch知识图谱房源推荐系统》开题报告 一、选题背景与意义 1.1 选题背景 随着互联网的快速发展和大数据技术的广泛应用&#xff0c;房地产行业特别是房屋租赁市场迎来了前所未有的变革。房源信息的海量增长使得用户在寻找合适的房源时面临巨大挑战。传统的房源推荐…

集成电路学习:什么是IDE集成开发环境

IDE&#xff1a;集成开发环境 IDE&#xff0c;全称“Integrated Development Environment”&#xff0c;即集成开发环境&#xff0c;是一种用于提供程序开发环境的应用程序。它集成了代码编写、分析、编译、调试等多种功能于一体的开发软件服务套&#xff0c;为开发者提供了一个…

集成电路学习:什么是MPU微处理器

一、MPU&#xff1a;微处理器 MPU&#xff0c;全称Microprocessor Unit&#xff0c;即微处理器单元&#xff0c;是计算机系统中的核心部件之一。MPU是一种集成了中央处理器&#xff08;CPU&#xff09;、内存、外设控制器和总线接口等功能的芯片&#xff0c;为电子设备提供强大…

Linux驱动(五):Linux2.6驱动编写之设备树

目录 前言一、设备树是个啥&#xff1f;二、设备树编写语法规则1.文件类型2.设备树源文件&#xff08;DTS&#xff09;结构3.设备树源文件&#xff08;DTS&#xff09;解析 三、设备树API函数1.在内核中获取设备树节点&#xff08;三种&#xff09;2.获取设备树节点的属性 四、…

2024 World Conference of Computer and Information Security(WCCIS 2024)

文章目录 一、会议详情二、重要信息三、大会介绍四、出席嘉宾五、征稿主题六、咨询 一、会议详情 二、重要信息 大会官网&#xff1a;https://ais.cn/u/vEbMBz提交检索&#xff1a;EI Compendex、IEEE Xplore、Scopus截稿日期&#xff1a;2024年9月4日2024年9月27-29日 广西桂…

Rust模块std::thread

【图书介绍】《Rust编程与项目实战》-CSDN博客 《Rust编程与项目实战》(朱文伟&#xff0c;李建英)【摘要 书评 试读】- 京东图书 (jd.com) Rust到底值不值得学&#xff0c;之一 -CSDN博客 Rust到底值不值得学&#xff0c;之二-CSDN博客 Rust多线程编程概述-CSDN博客 12.…

合碳智能 × Milvus:探索化学合成新境界——逆合成路线设计

合碳智能&#xff08;C12.ai&#xff09;成立于2022年&#xff0c;致力于运用AI和具身智能技术&#xff0c;为药物研发实验室提供新一代智能化解决方案&#xff0c;推动实验室从自动化迈向智能化&#xff0c;突破传统实验模式与人员的依赖&#xff0c;解决效率和成本的瓶颈&…

1. GIS开发工程师岗位职责、技术要求和常见面试题

本系列文章目录&#xff1a; 1. GIS开发工程师岗位职责、技术要求和常见面试题 2. GIS数据工程师岗位职责、技术要求和常见面试题 3. GIS后端工程师岗位职责、技术要求和常见面试题 4. GIS前端工程师岗位职责、技术要求和常见面试题 5. GIS工程师岗位职责、技术要求和常见面试…

Leetcode每日刷题之76.最小覆盖子串(C++)

1.题目解析 本题的题目是给定两个字符串 s 和 t &#xff0c;找出在 s 中的某个最小子串保证该子串中包含所以 t 中出现的字母即可&#xff0c;并且该结果是唯一答案&#xff0c;找不到结果就直接返回空串即可 2.算法原理 关于本题的核心思路就是"滑动窗口"&#xff…

【Python 千题 —— 算法篇】首字母大写

Python 千题持续更新中 …… 脑图地址 👉:⭐https://twilight-fanyi.gitee.io/mind-map/Python千题.html⭐ 题目背景 在文本格式化和处理过程中,常常需要将字符串的首字母大写。这在各种场景中都有实际应用,例如在标题格式化、用户输入校验、生成显示友好的文本等场景中。…

CC6链漏洞

CC6链漏洞 一 cc链简介 CC链是Apache Commons Collections反序列化漏洞利用链的简称&#xff0c;它涉及将可以执行命令的函数&#xff08;如Runtime.getRuntime().exec("calc.exe")&#xff09;序列化为对象流并转化为文件流存储在文件中&#xff0c;然后通过反序列…

深度学习5从0到1理解RNN(包括LTSM,GRU等):内容丰富(上)

循环神经网络&#xff08;Recurrent Neural Network, RNN&#xff09; 是一种经典的深度学习网络结构&#xff0c;具有广泛的应用。其中&#xff0c;槽填充&#xff08;Slot Filling&#xff09;&#xff08;即识别自然语言中的特定信息&#xff09; 是其中一个应用场景&#x…

香橙派开启vnc

1连接香橙派 2. 更新系统 在SSH会话中&#xff0c;首先更新系统软件包列表并升级现有软件包&#xff1a; sudo apt update sudo apt upgrade3. 安装VNC服务器 安装VNC服务器软件&#xff0c;这里以x11vnc为例&#xff1a; sudo apt install x11vnc 出现如图输入如下代码即可…