java并发编程学习六——乐观锁CAS

news/2024/4/28 4:55:32/文章来源:https://blog.csdn.net/yx444535180/article/details/126931257

文章目录

  • 一、CAS原理
    • 1.1 无锁保护共享变量
      • 1.1.1 不安全模式实现
      • 1.1.2 有锁安全实现
      • 1.1.3 无锁安全实现
    • 1.2 cas工作方式
    • 1.3 CAS的效率和特点
  • 二、原子整数
  • 三、原子引用
    • 3.1 AtomicReference
    • 3.2 ABA问题
    • 3.3 AtomicMarkableReference
  • 四、原子累加器

一、CAS原理

CAS全称CompareAndSet或者CompareAndSwap,其中【比较-交换】的操作是原子的,下面先一个具体的案例

1.1 无锁保护共享变量

定义一个接口

interface Acount {void withdraw(int amount);Integer getBalance();//设置余额为10000,一千个线程每个线程减少10,正确结果应该为0static void demo(Acount acount) {List<Thread> threadList = new ArrayList<>();for (int i = 0; i < 1000; i++) {threadList.add(new Thread(() -> acount.withdraw(10)));}long start = System.currentTimeMillis();threadList.forEach(Thread::start);threadList.forEach(thread -> {try {thread.join();} catch (InterruptedException e) {e.printStackTrace();}});long end = System.currentTimeMillis();System.out.printf("执行时间:%d,余额:%d", end - start, acount.getBalance());}
}

1.1.1 不安全模式实现

不安全的实现

class AcountUnsafe implements Acount {private Integer balance;public AcountUnsafe(Integer balance) {this.balance = balance;}@Overridepublic void withdraw(int amount) {this.balance -= amount;}public Integer getBalance() {return balance;}public void setBalance(Integer balance) {this.balance = balance;}
}

测试

public class Test1 {public static void main(String[] args) {Acount acount = new AcountUnsafe(10000);Acount.demo(acount);}
}

结果:
在这里插入图片描述

1.1.2 有锁安全实现

给withdraw加锁即可

 @Overridepublic void withdraw(int amount) {synchronized(this){this.balance -= amount;}}

结果:
在这里插入图片描述

1.1.3 无锁安全实现

无锁采用原子整数AtomicInteger来实现

class AcountSafe implements Acount {private AtomicInteger balance;public AcountSafe(AtomicInteger balance) {this.balance = balance;}@Overridepublic void withdraw(int amount) {while (true) {int prev = this.balance.get();int value = prev - amount;// compareAndSet是JVM提供的本地方法,该方法具有原子性if (this.balance.compareAndSet(prev, value)) {break;}}}public Integer getBalance() {return balance.get();}}

结果
在这里插入图片描述
比较执行时间发现,无锁比有锁实现要快一点

1.2 cas工作方式

先分析下上述代码的时序图
在这里插入图片描述
CAS底层使用的lock compxchg指令(X86架构),在单核CPU和多核CPU下都能保证【比较-交换】的原子性。CAS必须借助volatile才能读到共享变量的最新值,从而完成【比较-交换】的效果,来看看AtomicInteger的源码,其中初始值value就是用volatile修饰的
在这里插入图片描述
注意,volatile只能保证可见性,让其他线程看到最新值,但不能保证原子性即不能解决多个线程的指令交错问题。

1.3 CAS的效率和特点

CAS的作用是用无锁来实现多线程对共享变量操作的安全性,由于需要不停重试它也不是一定能提高效率。
CAS的效率:

  • 无锁的情况下,即使重试失败,线程仍然在高速运行。synchronized加锁会让线程进入阻塞,发生上下文切换。
  • 线程上下文切换,好比赛车在跑动高速运行时,需要先刹车减速停下之后再被唤醒之后重新启动、加速,代价比较大。
  • 无锁情况下,线程要保持运行,需要CPU支援。在单核CPU下,线程不加锁也会由于时间片使用完,发生上下文切换。因此,CAS需要在多核下才能发挥优势,而且线程数最好不要超过CPU核数。

CAS的特点:

  • CAS基于乐观锁的思维实现,不怕别人修改共享变量,修改了没关系,自己再重试
  • synchronized基于悲观锁的思维,加锁之后不允许别人修改共享变量,除非自己修改完释放锁,别人才有机会
  • CAS体现的是无锁并发、无阻塞并发。由于没有加锁,不会发生阻塞,从而提高效率。但是在竞争激烈的情况下,会发生大量的无效重试,反而会影响效率。

二、原子整数

AtomicInteger、AtomicBoolean、AtomicLong,这三个比较类似,都可以看做是对一个整数的封装。

public class AtomicIntegerTest {public static void main(String[] args) {AtomicInteger i = new AtomicInteger(0);//自增等价于++iSystem.out.println(i.incrementAndGet());//i++System.out.println(i.getAndIncrement());System.out.println(i.get());//类似的还有--i,i--//增加指定的数System.out.println(i.addAndGet(3));System.out.println(i.getAndAdd(3));System.out.println(i.get());//其他计算,乘法等复杂运算System.out.println(i.updateAndGet(x -> x * 2 - 1));System.out.println(i.getAndUpdate(x -> x * 2 - 1));System.out.println(i.get());//自己实现updateAndGetSystem.out.println(updateAndGet(i,x->(x-1)*2));}private static AtomicInteger updateAndGet(AtomicInteger i, IntUnaryOperator intUnaryOperator) {int prev;do {prev = i.get();} while (!i.compareAndSet(prev, intUnaryOperator.applyAsInt(prev)));return i;}
}

三、原子引用

3.1 AtomicReference

使用AtomicReference实现Account

public class AtomicReferenceTest {public static void main(String[] args) {Acount acount = new AccountBigDecimalSafe(new BigDecimal("10000"));Acount.demo(acount);}
}class AccountBigDecimalSafe implements Acount {private AtomicReference<BigDecimal> balance;AccountBigDecimalSafe(BigDecimal balance) {this.balance = new AtomicReference<>(balance);}@Overridepublic void withdraw(int amount) {while (true) {BigDecimal prev = balance.get();BigDecimal next = prev.subtract(new BigDecimal(amount));if (balance.compareAndSet(prev, next)) {break;}}}@Overridepublic Integer getBalance() {return balance.get().intValue();}
}

3.2 ABA问题

字符串A,先改成了B又改回A,主线程发现字符串还是A,就改成了C。一般ABA问题不会有什么影响,但在实际工作中还是注意。

public class AbaTest {public static void main(String[] args) {AtomicReference atomicReference = new AtomicReference("A");System.out.println(atomicReference);new Thread(() -> {if (atomicReference.compareAndSet("A", "B")) {System.out.println(atomicReference);}if (atomicReference.compareAndSet("B", "A")) {System.out.println(atomicReference);}}).start();try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}if (atomicReference.compareAndSet("A", "C")) {System.out.println(atomicReference);}}
}

3.3 AtomicMarkableReference

可以使用AtomicMarkableReference解决ABA问题

public class AbaTest2 {public static void main(String[] args) {AtomicMarkableReference<String> atomicReference = new AtomicMarkableReference("A",false);System.out.println(atomicReference.getReference());new Thread(() -> {if (atomicReference.compareAndSet("A", "B",false,true)) {System.out.println(atomicReference.getReference());}if (atomicReference.compareAndSet("B", "A",false,true)) {System.out.println(atomicReference.getReference());}}).start();try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}if (atomicReference.compareAndSet("A", "C",false,true)) {System.out.println(atomicReference.getReference());}}
}

AtomicMarkableReference增加了是否改动过的标记,只要改动过,并被标记上,后面的改动就无法生效。

四、原子累加器

原子累加器,指的是累加操作是原子的,java8之前只能使用原子整数类的incrementAndGet方法,java8新增了LongAdder(并发大神狗哥,Doug Lea编写),专门用于累加操作,效率提升5倍。

public class LongAddrTest {public static void main(String[] args) {for (int i = 0; i < 5; i++) {addr(() -> new AtomicLong(0), (AtomicLong::incrementAndGet));}for (int i = 0; i < 5; i++) {addr(LongAdder::new, LongAdder::increment);}}static <T> void addr(Supplier<T> supplier, Consumer<T> action) {T t = supplier.get();List<Thread> threads = new ArrayList<>(4);long start = System.currentTimeMillis();for (int i = 0; i < 4; i++) {threads.add(new Thread(() -> {for (int j = 0; j < 500000; j++) {action.accept(t);}}));}threads.forEach(Thread::start);threads.forEach(thread -> {try {thread.join();} catch (InterruptedException e) {e.printStackTrace();}});long end = System.currentTimeMillis();System.out.println("耗时:" + (end - start));}
}

测试结果:
在这里插入图片描述
LongAddr性能提升的原因是拆分了多个累加单元[cell0]…,当竞争激烈的时候,累加分散到多个cell减少失败重试,最后将结果汇总,最终减少竞争提高效率。

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

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C++学习】C++入门知识(上)

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《C学习》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; 到这里&#xff0c;本喵的C语言学习暂时就告一段落了&#xff0c;开始C的学习了&#xff0c;同样的&a…

Ubuntu安装opencv4(c++)遇到的问题及解决方法

安装教程&#xff0c;参考 Ubuntu 18.04安装c版OpenCV4 问题&#xff11;&#xff1a;opencv无法下载IPPICV的问题 ippicv_2020_lnx_intel64_20191018_general.tgz 解决办法&#xff1a;解决编译opencv时&#xff0c;卡在IPPICV: Download: ippicv_2020_lnx_intel64_20191018…

hive开启自动转化common join和map join 带来的问题

背景&#xff1a; 我们采用的hive版本是3.1.2属于较新版本&#xff0c;此版本下hive本身默认开启map join 相关配置 hive默认开启map join&#xff0c;涉及的配置如下&#xff1a; hive.auto.convert.jointrue错误分析 错误发生在我们使用曝光转化明细宽表和素材维表进行j…

Bunifu UI WinForms 6.0.1 Crack

现代强大的设计元素 无论您是设计简单的 UI 还是需要高级用户界面和用户体验控件和组件&#xff0c;Bunifu 框架都配备了实现任何现代设计所需的一切。 用户界面和用户体验 您可以设计的内容没有限制 画廊 通过 Bunifu Rating 从您的应用中获取反馈 画廊 使用 Bunifu 面板…

【初学者入门C语言】之while、do-while、break及continue语句(五)

个人主页&#xff1a;天寒雨落的博客_CSDN博客-python,c,安装教程领域博主 &#x1f4ac; 刷题网站&#xff1a;一款立志于C语言的题库网站蓝桥杯ACM训练系统 - C语言网 (dotcpp.com) 特别标注&#xff1a;该博主将长期更新c语言内容&#xff0c;初学c语言的友友们&#xff0…

javascript: 复制对象时的深拷贝及浅拷贝(chrome 105.0.5195.125)

一,js代码<html> <head><meta charset="utf-8"/><title>测试</title> </head> <body><button onclick="assign()">无效:变量直接赋值</button><br/><br/><br/><button oncli…

Android 资源文件存放位置 Drawable 与 Mipmap 区别

Drawable Drawable 文件夹存储 bitmap 文件(png, jpeg, gif)、9-patch 文件 和 xml 文件&#xff0c;这些文件用于描述包含多种状态 (normal, pressed, focused) 的可绘制形状或可绘制对象。 android 的 drawable 文件一共可以有&#xff1a; drawable-ldpi (低密度) drawable-…

如何根治 Script Error.

作者&#xff1a;卢峰&#xff08;清锐&#xff09; 本文简要介绍了 Script Error 问题的来龙去脉&#xff0c;但也不局限于 Script Error&#xff0c;对于通用的系统性问题&#xff0c;应该找到系统性解决方案&#xff0c;进而治标治本。 Script Error 原因与当前解法 受浏览…

第一个spring项目

第一个spring项目 1、maven依赖导入 <dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.3.22</version> </dependency> <dependency><groupId>junit</gro…

计算机毕设源码网站基于SpringBoot的阳光线上交友系统

&#x1f345;文末获取联系&#x1f345; 目录 一、项目介绍 二、开题报告 三、截图 四、源码获取 一、项目介绍 基于SpringBoot的阳光线上交友系统-计算机毕设java毕业设计项目源码-可定制-IT实战课堂_哔哩哔哩_bilibili项目资料网址: http://www.itszkt.com毕业设计…

Python 内存管理的工作原理你了解吗?

Python 为开发者提供了许多便利&#xff0c;其中最大的便利之一是其几乎无忧的内存管理。开发者无需手动为 Python 中的对象和数据结构分配、跟踪和释放内存。运行时会为你完成所有这些工作&#xff0c;因此你可以专注于解决实际问题&#xff0c;而不是争论机器级细节。 尽管如…

唯杰地图之前端CAD图GIS数据访问权限配置

前言 数字经济时代,数据要素的价值日益凸显,与之相应的,数据安全问题也越来越受到重视。唯杰地图 VJMAP为CAD图或自定义地图格式WebGIS可视化显示开发提供的一站式解决方案,支持的格式如常用的AutoCAD的DWG格式文件、GeoJSON等常用GIS文件格式,它使用WebGL矢量图块和栅格瓦…

一文清晰讲明白DDD(领域驱动设计)的知识点

什么是DDD DDD&#xff08;领域驱动设计&#xff09;是一种处理高度复杂领域的设计思想&#xff0c;是一种架构设计方法论&#xff0c;是一种设计模式。以高内聚低耦合为目的&#xff0c;把一个复杂的软件应用系统中各个部分进行一个很好的拆解和封装&#xff0c;对软件系统进…

运算放大器积分电路上并联的电阻什么作用

学过模电的同学对运放积分电路应该都不会陌生&#xff0c;基本电路如下图中所示 积分电路主要是用来进行波形变换&#xff0c;放大电路失调的消除&#xff0c;以及反馈控制中的积分补偿。 常用积分电路将方波变幻成三角波&#xff0c;或者正弦波变成余弦波&#xff0c;今天我们…

拍照识别花草软件有哪些?识别植物花草的软件哪个准?

不知道有没有小伙伴和我一样&#xff0c;好奇心比较旺盛&#xff0c;遇到问题都喜欢打破砂锅问到底。就连平时在路上遇到一些好看的花花草草时&#xff0c;我都想知道它是什么。但是花草这些就比较特殊&#xff0c;想了解它的身份&#xff0c;光靠描述可行不通。借助识别工具来…

UEC++ 代理/委托

代理&#xff1a; 代理可以帮助我们解决一对一或是一对多的任务分配工作。主要可以帮助我们解决通知问题。我们可以通过代理完成调用某一个对象的一个函数&#xff0c;而不直接持有该对象的任何指针。代理就是为你跑腿送信的&#xff0c;你可以不用关心给送信的目标人具体是谁…

异步线程使用Request存在问题

概述 如果我们将request传递到异步线程中使用&#xff0c;可能获取不到参数&#xff0c;并且会导致后续的请求&#xff0c;使用到这个线程也会出问题。 原因就是request对象会被重复使用。 源码分析 1、获取参数 先看一个非常重要的方法&#xff0c;getParameter 方法调用第…

计算机毕业设计之java+javaweb的美容院管理系统

计算机毕业设计之javajavaweb的美容院管理系统 项目介绍 系统权限按管理员、用户、医生和美容师这四类涉及用户。 (a) 管理员&#xff1a;进入系统可以实现主页、个人中心、用户管理、医生管理、美容师管理、项目部门管理、项目类型管理、产品分类管理、产品信息管理、医美项目…

JavaEE:进程调度的基本过程

目录 进程是什么? 操作系统对进程的调度 2.1 PCB中的信息 2.2 进程的调度是如何进行的呢? 并行: 并发: 总结: 进程是什么? 如果想了解进程调度的基本过程,我们首先要了解的是进程是什么? 咱们可以在任务管理器中看到 这一切跑起来的程序就是进程! 操作系统对进程的调…

22.this指针

1.this指针工作原理 我们知道,c++的数据和操作也是分开存储,并且每一个非内联成员函数(non-inline member function)只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码那么问题是:这一块代码是如何区分那个对象调用自己的呢?c++通过提供特殊的对象指针,this…