【JUC进阶】04. 无锁CAS

news2024/12/24 10:36:26

目录

1、前言

2、CAS概念和原理

2.1、什么是CAS

2.2、CAS原理

2.3、工作方式

2.4、无锁CAS优势

3、unsafe类

4、ABA问题

4.1、什么是ABA问题

4.2、解决ABA问题

4.2.1、版本号机制

4.2.2、AtomicStampReference

5、CAS适用的场景


1、前言

无锁的Compare and Swap(CAS)操作是一种高效的并发编程技术,通过原子性的比较和交换操作,实现了无锁的线程同步。在我之前的文章《简单理解CAS》icon-default.png?t=N5K3https://blog.csdn.net/p793049488/article/details/111404166?ops_request_misc=&request_id=f3a76ee1fc90483da433bfe9c43b7ce8&biz_id=&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~koosearch~default-1-111404166-null-null.268^v1^control&utm_term=cas&spm=1018.2226.3001.4450中已经有介绍过关于CAS了,今天我们再来重新回顾以及整理一遍。

2、CAS概念和原理

2.1、什么是CAS

CAS(Compare and Swap)是一种无锁并发算法,用于解决并发编程中的原子性操作问题。它是一种基于硬件指令的原子操作,能够在多线程环境下实现数据的原子性操作,而不需要使用传统的锁机制。

CAS(V, E, N)操作包括三个参数:

  • 内存地址V
  • 期望值E
  • 更新值N

JDK中实现的compareAndSet():

/**
 * Atomically sets the value to the given updated value
 * if the current value {@code ==} the expected value.
 *
 * @param expect the expected value  期望的值
 * @param update the new value   更新的值
 * @return {@code true} if successful. False return indicates that
 * the actual value was not equal to the expected value.
 */
public final boolean compareAndSet(int expect, int update) {
    // valueOffset: 内存中的值
    // expect: 期望的值
    // update: 更新的值
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

当V == E值时,才会将N赋值给V;如果V!=E时,说明已经有别的线程做了更新,当前线程什么都不做(一般是一种自旋的操作,不断的重试-----也是自旋锁)。

CAS操作是一个原子性操作,它在执行过程中不会被其他线程中断。因此,当多个线程同时执行CAS操作时,只有一个线程能够成功执行操作,其他线程将会失败。失败的线程可以选择重试操作或采取其他策略。

2.2、CAS原理

CAS操作的原理是基于底层硬件的支持,通常是通过处理器提供的原子性指令来实现的。这些指令可以保证对共享变量的读取和更新操作是原子性的,不会被其他线程干扰。

在执行CAS操作时,硬件会比较内存地址V的当前值和期望值A,并根据比较结果来决定是否更新内存地址V的值。

CAS操作的优势在于它避免了传统锁机制的开销,如线程阻塞和上下文切换。它能够在无锁的情况下实现原子性操作,提供更高的并发性能和更低的延迟。

在Java中,CAS操作主要通过java.util.concurrent.atomic包下的原子类来实现,如AtomicInteger、AtomicLong等。这些原子类封装了CAS操作,提供了一种线程安全的方式来操作共享变量,避免了手动使用锁的复杂性。

2.3、工作方式

CAS(V, E, N)工作方式基于以下几个基本操作。

  1. 读取操作:
    • 首先,读取内存地址V的当前值,记为当前值C。
  2. 比较操作:
    • 检查当前值C是否等于期望值E,如果相等,则继续执行后续步骤。如果不相等,则说明其他线程已经修改了内存地址V的值,操作失败。
  3. 交换操作:
    • 如果当前值C等于期望值E,将内存地址V的值更新为新值N。这个更新操作是原子性的,确保只有一个线程能够成功地更新内存地址V的值。

在执行CAS操作时,处理器提供了特定的原子指令,如compareAndSet,用于执行比较和交换操作。这些原子指令可以确保对共享变量的读取和更新是原子性的,不会被其他线程干扰。

示例:

import java.util.concurrent.atomic.AtomicInteger;

public class CASExample {
    private static AtomicInteger counter = new AtomicInteger(0);

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                int oldValue = counter.get();
                int newValue = oldValue + 1;
                while (!counter.compareAndSet(oldValue, newValue)) {
                    // 如果CAS操作失败,则重试
                    oldValue = counter.get();
                    newValue = oldValue + 1;
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                int oldValue = counter.get();
                int newValue = oldValue - 1;
                while (!counter.compareAndSet(oldValue, newValue)) {
                    // 如果CAS操作失败,则重试
                    oldValue = counter.get();
                    newValue = oldValue - 1;
                }
            }
        });

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Counter value: " + counter.get());
    }
}

2.4、无锁CAS优势

相对于传统的锁机制,它具有以下几个优势:

  1. 高性能:无锁CAS操作避免了传统锁机制中的线程阻塞和上下文切换的开销,因此具有更高的并发性能。由于无锁操作通常不需要等待其他线程的释放或唤醒,因此能够更充分地利用多核处理器的计算能力。
  2. 线程安全:CAS操作提供了一种线程安全的方式来操作共享变量。通过原子性的比较和交换操作,可以确保对共享变量的读取和更新是原子性的,避免了数据竞争和并发访问的问题。
  3. 非阻塞算法:无锁CAS操作是一种非阻塞算法,它不会导致线程的阻塞或休眠。相比于使用锁的方式,无锁算法能够更好地适应高并发环境,并减少线程的等待时间。
  4. 无饥饿现象:由于无锁CAS操作不会导致线程的阻塞,因此不存在饥饿现象。即使某个线程的CAS操作失败,它也可以继续尝试,直到成功为止,不会因为其他线程一直持有锁而无法执行。
  5. 缩小锁粒度:无锁CAS操作可以将锁的粒度缩小到变量级别,而不是整个代码块或对象级别。这意味着多个线程可以同时操作不同的共享变量,提高了并发性和系统的吞吐量。

3、unsafe类

按照惯例,以上概念性的内容我们读完之后。要开始看CAS本质的东西了。从compareAndSet的实现来看,他调用的是:

unsafe.compareAndSwapInt(this, valueOffset, expect, update);

那么unsafe是啥?

在Java中,sun.misc.Unsafe是一个强大的、直接操作内存和执行低级别操作的工具类。它提供了一些底层操作,可以绕过Java语言的限制,直接操作内存,实现一些高级特性。

Unsafe类中的方法可以用于执行一些不安全的操作,比如直接操作内存、分配和释放内存、对象的创建和销毁、线程挂起和恢复等。它可以被认为是一种"黑魔法",因为它绕过了Java语言的安全性和限制,提供了对底层操作的直接控制。

在CAS操作中,compareAndSwapXXX系列方法就是用Unsafe类来进行的。这些方法可以直接操作内存中的值,并在满足特定条件时进行原子性的更新。通过Unsafe类提供的CAS操作,可以实现无锁算法,避免使用传统的锁机制,提高并发性能。

从unsafe实现的几个cas相关操作方法来看,使用了native方法,来间接访问硬件底层的功能。native具体方法使用C++实现。sun.misc.Unsafe提供了三个CAS操作,从方法名即可看出,分别针对Object类型、int类型和long类型。

public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

需要注意的是,Unsafe类的使用需要谨慎,因为它绕过了Java的安全检查和内存模型,可能导致不可预测的结果和潜在的安全问题。在一般情况下,应该优先使用高级抽象和标准库提供的线程安全机制,只有在特定需求下,且对其使用有深入的了解和必要的安全措施时,才应考虑使用Unsafe类。

4、ABA问题

CAS固然性能很强,但是ABA问题是经常被提及的。什么是ABA问题?

4.1、什么是ABA问题

ABA问题是指在CAS操作过程中,共享变量的值从A经过一系列操作变为B,然后再经过一系列操作又恢复为A。这样的操作序列可能导致CAS操作无法察觉到中间值的变化,从而造成意外的结果。

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;

 /**
 * 贵宾卡充值案例模拟。
 * 当余额不足20的时候,充值20
 * 另一条线程,当金额大于10的时候,消费10
 * @author Shamee loop
 * @date 2023/6/17
 */
public class ABAProblemExample {
    static AtomicReference<Integer> money = new AtomicReference<>(15);
    
    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            Integer m = money.get();
            new Thread() {
                public void run() {
                    while (true) {
                        if (m < 20) {
                            if (money.compareAndSet(m, m + 20)) {
                                System.out.println("余额小于20,充值成功。余额=" + money.get());
                                break;
                            }
                        } else {
                            System.out.println("余额大于20,无需充值");
                            break;
                        }
                    }
                }
            }.start();
    
            new Thread() {
                public void run() {
                    while (true) {
                        Integer m = money.get();
                        if (m > 10) {
                            System.out.println("余额大于10");
                            if (money.compareAndSet(m, m - 10)) {
                                System.out.println("消费10元,余额=" + money.get());
                                break;
                            }
                        } else {
                            System.out.println("余额不足");
                            break;
                        }
                    }
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }.start();
        }
    }
}

执行结果:

这里多充值了一次20。原因就是账户余额被反复修改,修改后值等于原来的值,误以为没有被修改过,所以导致CAS无法正确判断当前数据状态。

4.2、解决ABA问题

4.2.1、版本号机制

对象内部多维护一个版本号,每次操作的同时版本号+1;CAS原子操作时,不只是判断值的状态,也判断版本号是否等于原来的版本号;就算值相等,版本号不等,也判断为被线程修改过。

4.2.2、AtomicStampReference

Java提供了AtomicStampedReference类,它在CAS操作中使用了额外的标记(stamp)来区分不同的操作序列,避免了ABA问题的出现。

/**
 * @author Shamee loop
 * @date 2023/6/20
 */

import java.util.concurrent.atomic.AtomicStampedReference;

public class ABAProblemSolutionExample {
    static AtomicStampedReference<Integer> money = new AtomicStampedReference<>(15, 0);

    public static void main(String[] args) {
        Integer stamp = money.getStamp();
        for (int i = 0; i < 3; i++) {
            Integer m = money.getReference();
            new Thread() {
                public void run() {
                    while (true) {
                        if (m < 20) {
                            if (money.compareAndSet(m, m + 20, stamp, stamp + 1)) {
                                System.out.println("余额小于20,充值成功。余额=" + money.getReference());
                                break;
                            }
                        } else {
                            System.out.println("余额大于20,无需充值");
                            break;
                        }
                    }
                }
            }.start();

            new Thread() {
                public void run() {
                    while (true) {
                        Integer m = money.getReference();
                        Integer stamp = money.getStamp();
                        if (m > 10) {
                            System.out.println("余额大于10");
                            if (money.compareAndSet(m, m - 10, stamp, stamp + 1)) {
                                System.out.println("消费10元,余额=" + money.getReference());
                                break;
                            }
                        } else {
                            System.out.println("余额不足");
                            break;
                        }
                    }
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }.start();
        }
    }
}

执行结果:

类似版本号机制,这里对象内部不仅维护了对象值,还维护了一个时间戳。当对应的值被修改时,同时更新时间戳。当CAS进行比较时,不仅要比较对象值,也要比较时间戳是否满足期望值,两个都满足,才会进行更新操作。

查看源码java.util.concurrent.atomic.AtomicStampedReference#compareAndSet:

/**
 * Atomically sets the value of both the reference and stamp
 * to the given update values if the
 * current reference is {@code ==} to the expected reference
 * and the current stamp is equal to the expected stamp.
 *
 * @param expectedReference the expected value of the reference
 * @param newReference the new value for the reference
 * @param expectedStamp the expected value of the stamp
 * @param newStamp the new value for the stamp
 * @return {@code true} if successful
 */
public boolean compareAndSet(V   expectedReference,
                             V   newReference,
                             int expectedStamp,
                             int newStamp) {
    Pair<V> current = pair;
    return
        expectedReference == current.reference &&
        expectedStamp == current.stamp &&
        ((newReference == current.reference &&
          newStamp == current.stamp) ||
         casPair(current, Pair.of(newReference, newStamp)));
}

5、CAS适用的场景

  1. 需要高并发性能:CAS操作是一种无锁的原子操作,不涉及线程的阻塞和切换,适用于高并发的场景。相对于传统的锁机制,无锁CAS可以减少线程的竞争和等待,提高系统的吞吐量和响应性能。
  2. 对共享变量的修改较少:无锁CAS操作适用于对共享变量的修改比较少的情况。由于CAS操作需要比较当前值与期望值,如果共享变量频繁发生变化,CAS操作的成功率会降低,性能会受到影响。因此,无锁CAS更适合于修改操作相对较少的场景。
  3. 无需持有锁的情况:CAS操作不需要持有锁,可以直接通过比较和交换来修改共享变量的值。这在一些情况下非常有用,比如在分布式环境中,通过CAS操作可以实现乐观锁,避免了对分布式锁的依赖。
  4. 数据竞争较小:无锁CAS操作需要保证数据的一致性,因此在存在大量的数据竞争情况下,CAS操作的成功率会降低,性能也会受到影响。因此,无锁CAS更适合于数据竞争较小的场景,例如对共享计数器的增减操作。

 

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

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

相关文章

libevent(9)通过libevent实时监听文件的更新

这里我们利用libevent监听centos系统上的login日志文件&#xff0c;文件路径&#xff1a;/var/log/secure。&#xff08;ubuntu下是"/var/log/auth.log"&#xff09; 代码如下 test_file.cpp&#xff1a; #include <iostream> #include <thread> #inclu…

数据迁移ETL工具分享

1.概述 ETL(是Extract-Transform-Load的缩写&#xff0c;即数据抽取、转换、装载的过程)&#xff0c;对于企业应用来说&#xff0c;我们经常会遇到各种数据的处理、转换、迁移的场景。 我汇总了一些目前市面上比较常用的ETL数据迁移工具&#xff0c;希望对你会有所帮助。 2. …

华为OD机试真题 Java 实现【核酸检测人员安排】【2023Q1 100分】

一、题目描述 在系统、网络均正常的情况下组织核酸采样员和志愿者对人群进行核酸检测筛查。 每名采样员的效率不同&#xff0c;采样效率为N人/小时。 由于外界变化&#xff0c;采样员的效率会以M人/小时为粒度发生变化&#xff0c;M为采样效率浮动粒度&#xff0c;M N * 10…

云数据库是未来趋势,亚马逊云科技位居Gartner报告“领导者”

最近,数据库领域发生了一个大事件,可以称得上是一座里程碑。全球最具权威的IT研究公司Gartner最近发布了一个消息:在2022年的全球DBMS市场份额中,亚马逊云科技的数据库超越微软,登顶第一。 亚马逊云科技、微软、Oracle这三巨头近几年一直排名前三,占据了全球DBMS超过三分之二的…

无法提取请求的数据。有关详细信息,请查看 vSphere Client 日志。vsan没法查询详细信息

解释&#xff1a; 根本原因是证书不一致&#xff0c;但是vc的证书和vsan他们不共用一个证书&#xff0c;所以需要保证集群证书的统一&#xff0c; &#xff0c;当时由于vc的证书到期后&#xff0c;只替换了vc的sts证书&#xff0c;在替换了STS证书之后&#xff0c;可能会导致…

LangChain 构建本地知识库问答应用

一、使用本地知识库构建问答应用 上篇文章基于 LangChain 的Prompts 提示管理构建特定领域模型&#xff0c;如果看过应该可以感觉出来 ChatGPT 还是非常强大的&#xff0c;但是对于一些特有领域的内容让 GPT 回答的话还是有些吃力的&#xff0c;比如让 ChatGPT 介绍下什么是 L…

地下水数值模拟软件有哪些??GMS、Visual MODFLOW Flex、FEFLOW、MODFLOW

目录 ①全流程GMS地下水数值模拟技能培养及溶质运移反应问题深度解析 ②Visual modflow Flex地下水数值模拟及参数优化、抽水实验设计与处理、复杂的饱和/非饱和地下水流分析 ③全流程各工程类型地下水环境影响评价【一级】方法与MODFLOW Flex建模 ④地下水热耦合模拟FEFLO…

亚马逊云科技通过“逆向工作法”,为客户解决数据库问题

最近,数据库领域发生了一个大事件,可以称得上是一座里程碑。全球最具权威的IT研究公司Gartner最近发布了一个消息:在2022年的全球DBMS市场份额中,亚马逊云科技的数据库超越微软,登顶第一。 亚马逊云科技、微软、Oracle这三巨头近几年一直排名前三,占据了全球DBMS超过三分之二的…

8.7 实现TCP通讯

目录 socket函数 与 通信域 套接字类型与协议 bind函数 与 通信结构体 domain通信地址族 与 通信结构体 IPv4地址族结构体 通用地址族结构体 示例&#xff1a;为套接字fd绑定通信结构体addr listen函数 与 accept函数 socket函数 与 通信域 #include <sys/types.h&g…

网络初识知识小结

目录 IP地址 端口号 协议 协议分层 TCP/IP 五层模型 传输过程 接收过程 IP地址 IP地址主要用于标识网络主机、其他网络设备&#xff08;如路由器&#xff09;的网络地址 换句话说 IP是网络中主机的身份证,可以通过IP地址定位该主机在网络中的地址 端口号 在网络通信中…

子项目中程序报:java.lang.NoClassDefFoundError

1.项目结构 NoClassDefFoundError 的 java类是在父项目中导入的 &#xff0c;子项目继承了父项目&#xff0c;在子项目中新建的main 程序中使用了该java类 大家可以看下scope 是否是 provided&#xff0c;大家选上下图的设置就可以了&#xff0c;不同版本idea 可能有差距然后重…

中移链链账户、合约与资源关系介绍

中移链链账户是在中移链区块链上注册的用户标识&#xff0c;每个账户可以持有一定数量的资源。CPU资源是账户用于执行智能合约的计算能力配额&#xff0c;决定了账户可以使用的CPU计算时间。NET资源是账户的带宽配额&#xff0c;用于处理事务的传输和网络通信。RAM资源用于存储…

【SSO】单点登录方案

一、单点登录问题 由于Http请求是无状态的请求&#xff0c;服务器无法确认登录信息。当用户登录时&#xff0c;将用户信息存储到Session中&#xff0c;Session将认证的用户信息以Cookie方式返回给客户端。每次用户请求不同的业务系统&#xff0c;都会携带Cookie去请求。保证了…

CloudCompare

零、安装 软件包直接安装 帮助文档&#xff1a;http://www.cloudcompare.org/doc 官网&#xff1a;http://www.danielgm.net/cc/ 一、操作&#xff1a;分割、删除点 1、打开文件File–>Open 支持的格式&#xff1a; 2、旋转和移动点云 鼠标左键可以旋转、右键可以移动 定轴…

Yolov8改进---注意力机制:ICASSP2023 EMA基于跨空间学习的高效多尺度注意力、效果优于ECA、CBAM、CA | 小目标涨点明显

1.EMA介绍 论文:https://arxiv.org/abs/2305.13563v1 录用:ICASSP2023 通过通道降维来建模跨通道关系可能会给提取深度视觉表示带来副作用。本文提出了一种新的高效的多尺度注意力(EMA)模块。以保留每个通道上的信息和降低计算开销为目标,将部分通道重塑为批量维度,并将…

【Linux】ubuntu20.04上使用xrdp控制输入密码之后一直停顿不动,进不去桌面环境

一、问题背景 如下图所示&#xff0c;每次登录桌面一段时间&#xff0c;就会因为自动锁定机制而锁定账户。 重新去激活账户时&#xff0c;输入正确的密码&#xff0c;回车确定&#xff0c;之后就停留在上面那个界面了。 二、 解决方案 2.1 重启xrdp服务 这个方法&#xff…

支付宝沙箱支付详细教程(IDEA版)—2023最新版

&#x1f607;作者介绍&#xff1a;一个有梦想、有理想、有目标的&#xff0c;且渴望能够学有所成的追梦人。 &#x1f386;学习格言&#xff1a;不读书的人,思想就会停止。——狄德罗 ⛪️个人主页&#xff1a;进入博主主页 &#x1f5fc;专栏系列&#xff1a;无 &#x1f33c…

leetcode数据库题第七弹

leetcode数据库题第七弹 1581. 进店却未进行过交易的顾客1587. 银行账户概要 II1633. 各赛事的用户注册率1661. 每台机器的进程平均运行时间1667. 修复表中的名字1683. 无效的推文1693. 每天的领导和合伙人1729. 求关注者的数量1731. 每位经理的下属员工数量1741. 查找每个员工…

软件性能测试流程指南

1 编写目的 规范Performance Tesing性能测试过程中的活动&#xff0c;明确测试活动流程和过程中各组织、角色的职责&#xff1b;使性能测试工作有章可循&#xff0c;控制测试活动按照计划有效的进行&#xff0c;用于指导性能测试项目的实施&#xff0c;从流程和规范性上保证测…

详细讲解~接口测试实践

目录 前言&#xff1a; 接口测试 为什么介绍接口测试&#xff1f; 模块接口测试 web接口测试 前言&#xff1a; 接口测试是软件测试中的重要环节&#xff0c;它涉及对系统的API&#xff08;Application Programming Int…