首页 > 编程语言 >4 JAVA CAS

4 JAVA CAS

时间:2025-01-03 12:58:21浏览次数:6  
标签:JAVA CAS CPU 线程 result public 内存

我们在抢红包那篇文章讲到CAS,是java的乐观锁的一种,我们简单介绍下CAS

CAS的底层原理是lock cmpxchg 指令(X86 架构)在单核和多核CPU下都能保证比较和交换的原子性

程序是在单核处理器上运行,会省略 lock 前缀,单处理器自身会维护处理器内的顺序一致性,不需要 lock 前缀的内存屏障效果

程序是在多核处理器上运行,会为 cmpxchg 指令加上 lock 前缀。当某个核执行到带 lock 的指令时,CPU 会执行总线锁定或缓存锁定,将修改的变量写入到主存,这个过程不会被线程的调度机制所打断,保证了多个线程对内存操作的原子性

CAS的全称表现在java上sun.misc.Unsafe类的方法。调用 UnSafe 类中的 CAS 方法,JVM 会实现出 CAS 汇编指令,这是一种完全依赖于硬件的功能,实现了原子操作

java cas 例子

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public class Test1 {
public static void main(String[] args) throws InterruptedException {
Account account = new AccountCas(10000);
Account.demo(account);
}
}
class AccountCas implements Account {
private AtomicInteger balance;
public AccountCas(int 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;
}
}
}
}
interface Account {
// 获取余额
Integer getBalance();
// 取款
void withdraw(Integer amount);
/**
* 方法内会启动 1000 个线程,每个线程做 -10 元 的操作
* 如果初始余额为 10000 那么正确的结果应当是 0
*/
static void demo(Account account) {
List ts = new ArrayList();
for (int i = 0; i < 1000; i++) {
ts.add(new Thread(() -> {
account.withdraw(10);
}));
}
long start = System.nanoTime();
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");
}
}

在上述代码中,我们设置一个账户余额有10000元,然后设置1000个线程,每个线程转出10元,正确结果这里应该是0,AccountCas类中,我们定义了一个AtomicInteger对象,CAS的底层操作就是如withdraw函数中所示,获取到当前的值和将要修改的值,然后做一个比较并设置的操作,将Account对象的最新值和当前线程拿到的当前值做对比,如果相等,则返回true,是没有问题的,如果不相等,那么获取当前值,在while(true)中再次执行一次流程。

我们看AtomicInteger的源码,如截图所示
在这里插入图片描述

我们给int值加了volatile修饰,才能保证值的原子性操作

volatile 指令重排序

volatile 防止指令重排序,确保操作的顺序性。
volatile 确保变量的修改对所有线程立即可见,避免了线程读取到过期数据的问题。
结合使用 volatile 和 CAS,可以在保证线程安全的前提下,避免使用锁,从而提高并发性能。

我们看下面的例子:
//代码1
package com.test;
import java.util.concurrent.TimeUnit;
public class ThreadDemo extends Thread {
//声明一个变量
private boolean result = false;
public boolean getResult(){
return result;
}
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//在线程运行时改变其变量值
result = true;
System.out.println(result);
}
}

//代码2
package com.test;
public class VolatileDemoOne {
public static void main(String[] args) {
ThreadDemo threadDemo = new ThreadDemo();
threadDemo.start();
while (true){
if(threadDemo.getResult()){
System.out.println(“11111111”);
}
}
}
}

正常情况下result在线程中已被修改为true,此时控制台将会一直打印11111111,但是此时却没有打印我们预期的结果.这就牵扯到我们的JMM内存模型

计算机模型

在我们现在多核CPU的情况下,多个CPU对我们主内存数据进行读写操作,为了提高其运行速率,将各个cpu读取到的数据存储到与CPU相对应的高速缓存中,这样就实现了各个CPU在自己的高速缓存中对数据进项操作,大大提高了运行效率,为了防止两个高速缓存中的数据不一致问题,计算机采取缓存一致性协议来保证每个CPU对应的高速缓存中的数据与主内存一致。如下图所示:

在这里插入图片描述

JMM内存模型

在这里插入图片描述

我们可以由图得知,JMM内存模型与我们计算机模型非常相似,那么是如何出现上述原因的呢?

首先当我们启动两个线程去获取变量数据时,都会去主内存中获取数据,并写入到自己的本地内存中,而ThreadDemo线程此时将自己的本地内存中的result改为false,并将数据同步到主内存中,但是此时我们的main线程还是在操作自己工作空间中的老数据,main线程ThreadDemo线程之间不能相互读取各自工作内存中的数据,而main线程读取到的result为false,出现我们控制台的结果

我们把上面的代码 private boolean result = false;修改为private volatied boolean result = false;再次执行代码 就能得到我们预期的结果了

我们加上这个修饰后,result 的值放在主内存中而且不是原来线程的本地缓存中读取。所以达到了我们预期的效果。关于禁止指令重排序的内容,有兴趣的可以去查找相关的资料,这里不再熬述。

CAS和Synchronized

我们在前面的博客中提到Synchronized属于悲观锁,在一个线程未执行完方法的时候,其他线程不能获取到资源并执行。CAS使用比较并交换的方式在未加锁的情况下,实现线程安全的操作。

CAS的ABA问题

我们看完上面的源码应该知道CAS是通过比较并交换的方式达到锁操作的效果,如果在并发量非常高的情况下,不建议使用。因为不断的有线程去修改值,原来的线程又要比较,不通过只能重新执行。在并发非常高的场景使用反而会让性能急剧的下降,而且大量的消耗资源。

标签:JAVA,CAS,CPU,线程,result,public,内存
From: https://blog.csdn.net/yusongcao/article/details/144899712

相关文章

  • Java Redis多限流
    在现代Web应用中,限流(RateLimiting)是保护系统资源和防止滥用的重要机制。Redis由于其高性能和原子操作特性,成为实现限流的理想选择。本文将详细介绍如何在Java中使用Redis实现多种限流策略,包括固定窗口限流、滑动窗口限流和令牌桶算法。一、准备工作1.安装Redis确保Redis已经......
  • HTML5期末大作业:基于HTML+CSS+JavaScript仿蘑菇街购物商城设计毕业论文源码 (1)
    常见网页设计作业题材有个人、美食、公司、学校、旅游、电商、宠物、电器、茶叶、家居、酒店、舞蹈、动漫、服装、体育、化妆品、物流、环保、书籍、婚纱、游戏、节日、戒烟、电影、摄影、文化、家乡、鲜花、礼品、汽车、其他等网页设计题......
  • 大二Web课程设计——张家界旅游网站设计与实现(HTML+CSS+JavaScript)
    ......
  • 快速排序算法的 Java 实现与性能调优
    目录一、快速排序的基本原理二、快速排序的Java实现三、时间复杂度与空间复杂度四、总结引言排序是计算机科学中的基础问题之一,无论是在数据库查询、数据分析,还是在日常编程中,排序算法的选择都对性能有着重要的影响。快速排序(QuickSort)是最广泛使用的排序算法之一,......
  • 《刚刚问世》系列初窥篇-Java+Playwright自动化测试-9- 浏览器的相关操作 (详细教程)
    1.简介在自动化测试领域,元素定位是非常重要的一环。正确定位页面元素是测试用例能否成功执行的关键因素之一。playwright是一种自动化测试工具,它提供了丰富的元素定位方法,可以满足不同场景下的定位需求。前边宏哥已经通过不少的篇幅将playwright的元素定位的一些常用的基本方法和......
  • 基于Java异步处理的 USB 设备监控系统设计与实现:技术架构与业务场景分析
    1.引言随着智能设备和物联网技术的快速发展,USB设备在各行各业中的应用越来越广泛。从工业设备到个人电子产品,USB设备已经成为数据传输和设备连接的主流方式。然而,设备的动态插拔和状态变化的检测,成为了许多业务系统中的一个重要挑战。特别是在需要实时响应设备插拔事件......
  • Java反射机制与动态代理
    软件开发中,灵活性与扩展性是非常重要的需求,而Java的反射机制与动态代理正是实现这些特性的强大工具。反射机制让程序在运行时能够检查和操作类的信息,而动态代理则为方法调用提供了一种灵活的拦截机制。本文将深入探讨这两种机制的概念、原理、应用场景,并通过具体示例展示它......
  • nodejs+vue+expressd协同过滤算法的毕业生租房平台java+python+php-计算机毕业设计
    目录技术栈和环境说明具体实现截图预期达到的目标系统设计详细视频演示技术路线解决的思路性能/安全/负载方面可行性分析论证python-flask核心代码部分展示python-django核心代码部分展示研究方法感恩大学老师和同学源码获取技术栈和环境说明本系统以Python开发语言......
  • 浏览器在渲染时遇到javascript文件要怎么处理?
    在前端开发中,当浏览器在渲染网页时遇到JavaScript文件,它会按照一系列步骤来处理这些文件。以下是浏览器处理JavaScript文件的主要步骤:解析HTML文档:浏览器从服务器下载HTML文档,并开始解析它。当浏览器遇到<script>标签时,它会根据标签的属性(如src、async、defer、t......
  • 【Java中BigDecimal和Long对比】
    Java中BigDecimal和Long对比概述BigDecimal定义:BigDecimal是Java中提供的一个不可变的、任意精度的有符号十进制数类。它适合用于需要高精度和控制舍入行为的场景,例如货币计算。特点:支持任意精度的小数运算。提供了多种舍入模式。不可变性(immutable),每次操作都会返......