一文了解JAVA CAS
admin
2024-05-23 02:51:16
0

前言

首先我们看下面这一段代码,具体实现的功能是创建100个线程,每个线程对初始化为0的整数进行1万次+1操作,等待这100个线程跑完后输出这个整数的值,理论上这个整数最后输出的数值为100万。

import java.util.concurrent.CountDownLatch;public class AddTest {private volatile long i = 0;private void add() {i++;} private void println() {System.out.println(i);}private static final int THREAD_NUM = 100;public static void main(String str[]) throws Exception {CountDownLatch latch = new CountDownLatch(THREAD_NUM);AddTest addTest = new AddTest();for (int i = 0; i < THREAD_NUM; ++i) {new Thread(() -> {for (int j = 0; j != 10000; ++j) {addTest.add();}latch.countDown();}).start();}//等待100个线程跑完latch.await();addTest.println();}
}

实际上跑完后的结果为400131,显然和预期的100万不符合,多次跑还会结果不一样,但是结果都少于100万,为什么呢?

400131进程已结束,退出代码0

问题原因

因为i++并不是一个原子操作,更新整数i其实包含一下3个步骤:

  1. 读取当前值。
  2. 执行递增操作以获取更新的值。
  3. 将更新的值分配给字段引用。

这些都作为不同的任务执行,因此,当一个线程正在读取值时,另一个线程可能正在递增它,而另一个线程可能正在将更新的值分配给字段引用。为了加深理解,我们引入最简单的C语言i++的代码,如下。

int main()
{int i = 0;i++;return 0;
}

利用gcc工具将C语言代码转为汇编语言。

gcc -S test.c -o test.s

得到test.s文件,其中i++的汇编执行指令如下,更新i的步骤有3个,首先读取当前值,其次执行必要的操作以获取更新的值,再将更新的值分配给字段引用。

movl	-8(%rbp), %ecx
addl	$1, %ecx
movl	%ecx, -8(%rbp)

以上汇编是AT&T汇编语法,平时学习常见的是Intel汇编语法(mov,add等指令),具体这2个语法差异可以跳到这个链接看看,这里不展开介绍。https://sdasgup3.github.io/Intel_Vs_Att_format/

在之前的java代码中,我们预期为100万的结果执行流程应该如下,每个线程中的movl,addl,movl指令都是作为整体一起执行的,该线程执行完毕之后才到下一个线程执行。

指令
thread1-1movl -8(%rbp), %ecx
thread1-2addl $1, %ecx
thread1-3movl %ecx, -8(%rbp)
thread2-4movl -8(%rbp), %ecx
thread2-5addl $1, %ecx
thread2-6movl %ecx, -8(%rbp)

但是实际上执行的执行流程有可能如下面的表格一样。

thread1thread2
1movl -8(%rbp), %ecx
2movl -8(%rbp), %ecx
3addl $1, %ecx
4movl %ecx, -8(%rbp)
5addl $1, %ecx
6movl %ecx, -8(%rbp)

线程1先把当前的值(i=0)存到寄存器%ecx,然后轮到线程2把当前的值存到寄存器%ecx,这个时候2个线程获取到的当前值都是一样的,接着线程2把i+1的结果存到寄存器%ecx,再把寄存器%ecx的值取出来更新到i为1。然后轮到线程1接着进行addl和movl操作,由于线程1和2之前获取的当前值(i)一样,所以最终线程1也把i更新1。

解决方案之一CAS

为了解决以上并发问题,在JDK1.5之后,提供了CAS(全称Compare And Swap)操作,该操作由sun.misc.Unsafe类里面的compareAndSwapInt()和compareAndSwapLong()等几个方法包装提供。一句话概括该原理就是,根据内存地址获得期望值,更新的时候再次根据原来的内存地址获取值,如果和期望值一样,则进行更新,否则更新失败。

这里的内存地址是指对象地址+变量偏移值。

其中AtomicLong对象的实现就是基于CAS的,下面是核心的代码。

    /*** var1为对象地址,var2为变量偏移值*根据对象地址和变量偏移值就能获取到对象参数的值,该值为期望值var6。* */public final long getAndAddLong(Object var1, long var2, long var4) {long var6;do {//获取期望值,这里已经移交给外部程序执行了,是本地方法。var6 = this.getLongVolatile(var1, var2);//compareAndSwapLong也是本地方法,移交给外部的程序执行,看看更新那时候是不是还是var6,如果是就更新为var6 + var4,否则不更新。} while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));return var6;}

代码中的while循环,代表循环直到更新成功为止,因为每个线程执行一定有自己的执行任务,只是这个时刻不应该进行更新,如果缺少while循环会导致自增结果不准,因此在高并发下,会存在很多无效循环(划重点)。

所以我们可以使用AtomicLong对象来解决这个问题,更新后的代码如下。

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicLong;public class AddTest {public  AtomicLong i = new AtomicLong(0);private void add() {i.incrementAndGet();} private void println() {System.out.println(i.get());}private static final int THREAD_NUM = 100;public static void main(String str[]) throws Exception {CountDownLatch latch = new CountDownLatch(THREAD_NUM);AddTest addTest = new AddTest();for (int i = 0; i < THREAD_NUM; ++i) {new Thread(() -> {for (int j = 0; j != 10000; ++j) {addTest.add();}latch.countDown();}).start();}//等待100个线程跑完latch.await();addTest.println();}
}
1000000进程已结束,退出代码0

相关内容

热门资讯

盘前:科技股热潮降温 纳指期货... 来源:环球市场播报 周五,美国股指期货下跌。科技股走弱、美国国债收益率上升拖累大盘。科技板块近期大...
600096,拟投建1000万... 今日(5月15日),三大股指均收跌,全市场成交额为3.37万亿元,较上一个交易日缩量179亿元。收盘...
原创 应... 当地时间5月14日美股盘后,半导体设备达成应用材料(Applied Materials)公布了202...
歌手温岚被紧急送入ICU,主办... 歌手温岚原定于5月16日在上海举办巡回演唱会。15日,有消息称温岚因身体不适被紧急送医,随后,演唱会...
闪迪、美光越涨越便宜?股价暴涨... 存储芯片需求的爆炸式增长正在颠覆传统估值逻辑——股价越涨,闪迪和美光反而越便宜。 闪迪今年以来股价累...
监管部门“5·15”密集发声,... 监管新规密集发布,投资者保护防线再加固。 5月15日,证监会在北京举办2025年“5·15全国投资者...
纳指、标普500指数续创新高!... 美股三大指数集体收涨,纳指涨0.88%,标普500指数涨0.77%,道指涨0.75%。其中,纳指、标...
欧洲主要股指收盘集体下跌 英国富时100指数跌1.71%,法国CAC40指数跌1.72%,德国DAX30指数跌2.11%,富时...
巴宝莉去年扭亏盈利近两亿元,进... 英国奢侈品牌Burberry巴宝莉公布截至3月28日的2026财年业绩,释放明显复苏信号。集团营收同...
腾澎投资拟减持巨人网络不超3%... 巨人网络公告显示,公司控股股东一致行动人、第二大股东上海腾澎投资合伙企业(有限合伙)(下称“腾澎投资...
医疗健康领域投融资日报(5月1... 据亿欧数据统计,昨日(2026年5月14日)共披露23起投融资事件,涉及15家国内企业,8家国外企业...
债市ETF“工具箱”,解锁固收... 当前,市场波动有所加大,不确定性因素较多,单一资产投资模式难以有效应对市场起伏,引入固收类资产、优化...
招商蛇口股东会通过博时蛇口产园... 观点网讯:5月15日,招商蛇口2026年第一次临时股东会在公司总部会议室召开,会议由董事长朱文凯主持...
《学习时报》刊文:全球海洋可再... 海洋可再生能源一般指蕴藏于海水水面、水体及海床之中,可转化为电能的清洁能源类型,主要包括海上风能、潮...
数据看盘游资、量化抢筹多只机器... 沪深股通今日合计成交4353.39亿,其中澜起科技和中际旭创分居沪股通和深股通个股成交额首位。板块主...
土耳其BIST-100指数下跌... 土耳其BIST-100指数下跌1.8%,主要银行指数下跌2.4%。 来源:金融界AI电报
15分钟动态电价时代:园区光伏... 一、电价改革的“加速度”:从分时计费到现货波动 过去,工商业用户的电价表一年可能只调整几次,峰、平、...
湘潭上元产业港:多套成交 12... 湘潭上元产业港再迎成交热潮,近期3套优质厂房成功签约,多位企业家携手落子,以实力见证长株潭热土的产业...
4月新增人民币贷款跌入负区间,... 本报(chinatimes.net.cn)记者刘佳 北京报道 作为观察货币政策传导效率的核心窗口,4...
2.2/7.2馆展位图首发!5... 【2.2馆展位图】 【7.2馆展位图】 Bakery china 2.2馆部分 企业推介 22B...