网站首页 > java教程 正文
文章前记
程序员工作久了便可能整日忙碌于“增删改查”中,迷失方向,毫无进步。
该公众号致力于分享软件开发相关的原创干货,助你完成从程序员到架构师的进阶之路!
努力!做一个NB的Coder!
1 背景
在编程中,我们知道如果对一个变量读取然后写入这并不是一个原子操作,例如我们常用的i++操作,其实可以简单划分为以下三步:
以上三步操作时独立的,因此并不是原子化的。如果i变量在第1步和第3步之间被其他线程更改则会引发意向不到的结果。例如线程1对i=5进行i++操作时,线程2对变量i进行了写入操作,则还会发生混乱。如图所示:
此时我们看到,因为i++操作不是原子的,中间受到线程2的影响,使得i++的操作是错误的。
如果是原子化操作,则i++操作要么发生在线程2的操作之前,最终得到i=3;要么发生在线程2操作之后,得到i=4。而不能出现i=6这样的结果。
其实i++就是一个最简单的汇聚了读取和写入的操作,因此读取并写入操作并不是原子化的。可是,Java中确实能够将读取并写入操作封装为一个原子化操作。那它是怎样实现的呢?
我们先介绍一种粗暴的实现:使用同步块实现原子化。
2 使用同步块实现原子化
为了能实现i++操作的原子化,则最容易想到的就是使用同步块:
但这显然大材小用,将整个操作放置在同步块中,虽然借助同步块实现了原子化,但因为同步块的操作要进行锁的分配等,使得程序的整体效率受到了影响。
而我们知道Java中AtomicInteger类可以直接将读和写封装在一个原子操作中实现原子化的i++等操作,那它们是怎么实现的呢?我们接下来进行探讨。
3 硬件CAS操作
Java中AtomicInteger类便可以将读和写封装在一个原子操作中。那它是怎么实现的呢?
它使用的是CAS操作。CAS是compare and swap的缩写,中文可以翻译成:比较并交换。CAS操作来源于底层硬件领域。因为CAS能够极大地提高并发效率,因此在硬件设计领域,CAS这种操作就是存在的。例如在intel的CPU中,使用cmpxchg指令就可以实现CAS操作。我们详细介绍下CAS操作的原理:
CAS操作一共包含三个操作数:内存位置(V)、预期原值(A)和新值(B)。具体操作过程如下:
- 首先,返回V位置的原值R
- 如果内存位置的值R与预期原值A相匹配,那么处理器会自动将该位置值更新为新值
- 否则,处理器不做任何操作
无论结果如何,该操作总能拿到V处的原值R,如果R等于A,则说明V处的值已经被更新为了B;否则说明更新失败,V处的值依旧是R。因此,经过该操作后,可以知道CAS操作是否成功。
当然,还有一种CAS实现是直接返回操作是否成功的结果,即:如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值,并返回操作成功;否则,返回操作失败。
但是这与返回V位置原值的方式大同小异,而且返回原值R的实现方式不仅能获得操作是否成功的结果,还能顺便获得R值,因此更为常用。
4 Java中的CAS操作
在Java初期,java是无法直接利用这些硬件操作来提升并发性能的,直到后来Java本地方法(JNI)的出现,使得Java直接调用本地方法称为可能,在Doug Lea提供的cucurenct包得以实现。
在Java中如何实现呢?其实知道了CAS操作的原理,一下就变得简单起来。我们还是以i++操作为例,介绍CAS整个过程:
- 读取内存位置V的原值R
- 因为进行的是i++操作,因此CAS操作的预期原值A=R、新值B=R+1,进行CAS。
- 获取V的原值R
- 如果R等于A,更改成功,操作结束
- 如果R不等于A,则没有更改。但是获取当前时刻V的原值,将R重新设置为该值。则预期原值A=R、新值B=R+1,然后在继续进行CVS。
空口无凭,我们看一下AtomicInteger中i++操作的实现方法getAndIncrement():
而getAndAddInt()方法如下:
我们继续追踪到compareAndSwapInt()方法,分析该方法是一个本地方法。
稍加需要注意的是,可能由于其他原因,这里的compareAndSet采用的是返回操作是否成功的实现方式,而不是返回当前内存位置原值R的方式。因此在每一次进行CAS操作时,都需要先调用getIntVolatile(var1, var2)方法获取一次当前的变量值作为预期原值A。
因此,getAndIncrement()的具体操作就是:
- 获取当前值,在此基础上+1作为目标值
- 进行CAS操作
- 如果不成功,说明值不是最新的。重新获取值,不断再试。
如此一来,我们就知道了Java中是如何将读取和写入这两个操作封装成一个原子操作的。
5 总结
CAS的思想本质上就是乐观锁思想。即:假设情况都是乐观的,那么当前内存位置V的值是预期原值A,如果这样,请把内存位置V的值设置为新值B;否则,乐观的情况不成立,什么也别做。
乐观锁的思想减少了使用同步块等悲观锁的开销,但是本质是基于乐观锁思想的,因此肯定存在一些问题。
对与CAS引发的问题与解决办法,我们将会在下次文章中详细介绍。
关注我们!不错过每期的架构师干货!
猜你喜欢
- 2024-09-16 拼多多技术3面(Java研发):幻影读+Redis+分布式缓存+锁机制
- 2024-09-16 用java帮助你判断一个数是不是回文数
- 2024-09-16 java之解析复杂json(含源码)(java解析复杂json格式数据)
- 2024-09-16 “全栈2019”Java多线程第四十一章:读锁与写锁之间相互嵌套例子
- 2024-09-16 java开发神器IntelliJ IDEA CE 2019.2汉化版
- 2024-09-16 史上最强Java NIO入门:担心从入门到放弃的,请读这篇
- 2024-09-16 读Java实战(第二版)笔记01_Java的变化
- 2024-09-16 语言拟人向:来自Python、JAVA、C语言的“傲娇”自我介绍
- 2024-09-16 Java编程入门100例之四十五(读文件)
- 2024-09-16 读Java性能权威指南(第2版)笔记24_ Java飞行记录器JFR
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)