CAS实现原子操作的三大问题

举报
上善若水. 发表于 2022/09/11 23:39:35 2022/09/11
961 0 0
【摘要】 CAS实现原子操作的三大问题ABA问题:因为CAS需要操作值的时候,检查值有没有变化,如果没有变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号,在变量前面追加一个版本号,每次变量更新的时候把版本号加1。循环时间长开销大:自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。...

CAS实现原子操作的三大问题
ABA问题:因为CAS需要操作值的时候,检查值有没有变化,如果没有变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号,在变量前面追加一个版本号,每次变量更新的时候把版本号加1。
循环时间长开销大:自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。
只能保证一个共享变量的原子操作:对于多个共享变量的操作,循环CAS就无保证操作的原子性了,这个时候就可以用锁。
2、使用锁机制实现原子操作
锁机制保证只有获得锁的线程才能够操作锁定的内存区域。JVM内部实现了很多种锁机制,有偏向锁、轻量级锁和互斥锁。有意思的是除了偏向锁,JVM实现锁的方式都用了循环CAS,即当一个线程想要进入同步块的时候使用循环CAS的方式来获取锁,当它退出同步块的时候,使用循环CAS释放锁。
Java并发机制的实现
Java内存模型
Java线程之间的通信由Java内存模型(JMM,Java Memory Model)控制,JMM决定了一个线程的共享变量的写入何时对另一个线程可见。从抽象角度来看,JMM定义了线程和内存之间的抽象关系,线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存(也称工作内存),本地内存是JMM的一个抽象概念,并不是真实存在的。它涵盖了缓存、写缓冲区、寄存器以及其他的硬件和编译器优化。Java内存模型的抽象示意图如下:
重排序
在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排序。重排序分3种
1、编译器优化的重排序,编译器在不改变单线程程序语义的前提下,可以重排序语句的执行顺序。
2、指令集并行的重排序,现代处理器采用了指令集并行技术(流水线技术)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
3、内存系统的重排序,由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去时在乱序执行。
重排序对多线程的影响
public class ReorderExample {
int a=0;
boolean flag=false;

public void writer(){
	a=1;			//1
	flag=true;		//2
}

public void reader(){
	if(flag){		//3
		int i=a*a;	//4
	}
}

}
flag变量是个标记,用来标识变量a是否已被写入。这里假设有两个线程A和B,A首先执行writer()方法,随后B线程接着执行reader()方法。线程B在执行操作4时,能否看到线程A在操作1对共享变量a的写入呢
答案是:不一定能看到。
由于操作1和操作2没有数据依赖关系,编译器和处理器可以对这两个操作重排序;同样,操作3和操作4没有数据依赖关系,编译器和处理器也可以对这两个操作重排序。让我们先来看看,当操作1和操作2重排序时,可能会产生什么效果?请看下面的程序执行时序图:
操作1和操作2做了重排序。程序执行时,线程A首先写标记变量flag,随后线程B读这个变量。由于条件判断为真,线程B将读取变量a。此时,变量a还没有被线程A写入,在这里多线程程序的语义被重排序破坏了!

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

作者其他文章

评论(0

抱歉,系统识别当前为高风险访问,暂不支持该操作

    全部回复

    上滑加载中

    设置昵称

    在此一键设置昵称,即可参与社区互动!

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。