一文了解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

相关内容

热门资讯

斗金订购APP贵金属期货投资被...   斗金订购APP的投资者被广告宣传给诱导,注册就送什么现金,然后充值返现金卷等等这些宣传方式,都是...
哈易购APP非法期货交易欺骗投...   哈易购APP宣传可做白银铂金贵金属订购交易,但实际上并没有取得相关交易资质!哈易购APP本质上就...
消息称百度旗下昆仑芯瞄准500... 6 月 29 日消息,据《The Information》昨日援引知情人士消息,百度旗下 AI 芯片...
打造夏日消费新场景 第35届北... 北京商报讯(记者 翟枫瑞)6月29日消息,第35届北京国际燕京啤酒文化节新闻发布会在京举行。本届啤酒...
社保基金持仓数据出炉,一季度增... 最近各大上市公司一季度财报都公开了,咱们国家社保基金的持仓数据也全部曝光。目前社保拿着比亚迪价值44...
36氪首发 | 海思、中兴团队... 作者 | 乔钰杰 编辑 | 袁斯来 硬氪获悉,广州宸思通讯科技有限公司(以下简称“宸思科技”)近日完...
两天蒸发47亿市值!一纸税务通... 一纸税务通知书,能让一家百亿龙头两天蒸发47亿市值。 6月22日,北大荒(600598.SH)公告称...
SK海力士将投资1100万亿韩... SK集团会长崔泰源6月29日在韩国“三大重大计划”发布会上宣布,公司将投资1100万亿韩元扩大半导体...
两只A股,终止上市! 两家A股公司,即将摘牌。 6月29日,退市沪科(600608.SH)公告称,上海证券交易所将在202...
原创 M... 一家成立近十年的自动驾驶公司,在IPO时吸引了14家基石投资者认购近一半的发行股份,其中不乏奔驰、比...
基金忠言|国寿安保滤镜碎,三年... 图片来源:视觉中国 蓝鲸新闻6月29日讯(记者 祁和忠)保险系基金公司国寿安保总经理换人了。 6月2...
三星电机计划加码玻璃基板!相关... 6月29日,玻璃基板概念股午后有所回升, 华工科技(000988.SZ)逼近涨停, 彩虹股份(600...
拉萨海关持续壮大外贸经营主体 ...   新华网拉萨6月28日电(记者蒋梦辰)近日,记者从拉萨海关获悉,今年前5个月,西藏有进出口实绩的外...
机构:二季报临近,医药生物板块... 6月29日,华源证券发布了一篇医药生物行业的研究报告,报告指出,业绩期临近,产业链景气度有望再次迎来...
每日收评科创50放量涨超4.5... 财联社6月29日讯,三大指数全线收红,创业板指探底回升,科创50指数大涨4.61%。沪深两市成交额3...
6月多地土拍结构性升温:深圳单... 进入2026年6月,不少城市核心区地块集中诞生高溢价宗地,热度突出的城市包含深圳、杭州、长沙。 其中...
业绩炸裂!盛达资源半年预盈3.... 6月29日,贵金属矿山龙头盛达资源(000603.SZ)发布 2026 年半年度业绩预告,上半年业绩...
A股午后拉升三大股指收涨:半导... A股三大股指6月29日开盘涨跌互现。早盘沪强深弱,创指一度跌超2%。半导体午后拉升,带动两市上涨,沪...
原创 空... 前言 大家好,我是老金。 这几天,两幅极度割裂的画面放在一起,把我看笑了。 一边是在持续的热浪下,欧...