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

相关内容

热门资讯

疑似新模型海外惊艳!智谱再度飙... 格隆汇2月10日|延续昨日强势,港股市场AI概念股今日再度集体走强,其中,“全球大模型第一股”智谱(...
原创 特... 特朗普上任已逾一年,他推行的关税政策像一阵狂风,搅动了全球的经贸秩序。对于美国经济的未来走向,诺贝尔...
原创 一... 2026年2月9日晚的美股市场,上演了一场让很多投资者既兴奋又意外的行情。 本以为大涨之后总要歇一歇...
电商领域侵权问题获关注,知识产... 2月10日,知识产权保护概念持续拉升,截至发稿,成分股读客文化(301025.SZ)、中文在线(30...
原创 1... 12艘满载着俄罗斯乌拉尔原油的超级油轮,正像一群迷路的巨鲸,散落在从马六甲海峡到中国南海的广阔水域里...
凯思凯迪完成近5亿融资:中平资... 雷递网 乐天 2月10日 凯思凯迪宣布近期完成近5亿元新一轮融资,本轮融资由中平资本领投,国寿资本、...
美国出现小米YU7测试车?雷军... 近日,网上传出小米YU7 MAX测试车出现在美国道路的消息,难不成小米汽车要进军美国市场了? 事实...
2026-2032年中国食糖行... 共研网发布的《2026-2032年中国食糖行业深度调研与市场调查预测报告》共十二章。首先介绍了食糖行...
原创 美... 特朗普上台后不久,便对进口产品挥起了关税大棒。从钢铝到汽车零部件,一系列严苛的关税政策自2025年春...
盘中必读|字节旗下Seedan... 2月10日,AI短剧概念延续强势,荣信文化(301231)、捷成股份(300182)、欢瑞世纪(00...
2月25日起预约!申请退税别错... 近日,国家税务总局发布通告,明确2025年度个人所得税综合所得汇算清缴办理时间为2026年3月1日至...
再迎反弹!现货黄金重回5000... 贵金属再迎反弹。 2月9日,黄金、白银价格同步拉升。现货黄金再次突破关键阻力位,重回5000美元/盎...
YU7现身加州高速,小米会不会... 2月10日,雷军发文: 前段时间,一辆YU7行驶在美国加州的高速公路上,挂着当地的测试车牌。 很多人...
宁波迎来开年第一股!爱芯元智港... 转自:东南财金 2月10日,爱芯元智(0600.HK)正式于港交所主板挂牌上市,成为港股边缘计算AI...
2026年春节档新片预售票房已... 2月10日,市场早盘窄幅震荡,三大指数小幅下跌,北证50指数盘中跌超1%。沪深两市半日成交额1.39...
原创 俄... 俄罗斯黄金大量涌入中国,这背后究竟隐藏了怎样的玄机?根据2025年海关的数据,单单实物净进口量就高达...
亚太药业:聘任邱中勋为公司总经... 每经AI快讯,亚太药业2月9日晚间发布公告称,因公司控制权已发生变更,根据《股份转让协议》约定等相关...
原创 中... 我们中国的女富豪中,不乏靠着刻苦努力一步步爬上顶端的典型,也有不少依靠精准眼光与幸运投资一跃而成的成...
黄金交易提醒:美元疲软+央行“... 汇通财经APP讯——2026年2月的第二个星期,全球金融市场的心脏,似乎正随着那剧烈跳动。金价在50...
多措并举推动投资止跌回稳 国家统计局数据显示,2025年,全国固定资产投资同比下降3.8%。分领域看,基础设施投资下降2.2%...