首页 / 知识
可算是把volatile整明白了
2023-04-11 16:32:00

关于可见性
不多bb,码来
public class VolatileTest {
private static volatile int count = 0;
private static void increase() {
count++;
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 10000; j++) {
increase();
}
})。start();
}
// 所有线程累加完成后输出
while (Thread.activeCount() > 2) Thread.yield();
System.out.println(count);
}
}
代码很好理解,开了十个线程对同一个共享变量count做累加,每个线程累加1w次
count我们已经用volatile修饰,已经保证了count对十个线程在内存中的可见性,按理说十个线程执行完毕count的值应该10w
然鹅,运行多次,结果都远小于期望值
是哪个环节出了问题?
你肯定听过一句话:volatile只保证可见性,不保证原子性
这句话就是答案,但是依旧很多人没搞懂其中的奥秘
说来话长我长话短说,简单来讲就是 count++这个操作不是原子的,它是分三步进行
从内存读取 count 的值
执行 count + 1
将 count 的新值写回
要彻底搞懂这个问题,我们得从字节码入手
下面是increase方法编译后的字节码
看不懂没关系,我们一行一行来看:
GETSTATIC:读取 count 的当前值
ICONST_1:将常量 1 加载到栈顶
IADD:执行+1
PUTSTATIC:写入count最新值
ICONST_1和IADD其实就是真正的++操作
关键点来了,volatile只能保证线程在GETSTATIC这一步拿到的值是最新的,但当该线程执行到下面几行指令时,这期间可能就有其它线程把count的值修改了,最终导致旧值把真正的新值覆盖
懂我意思吗
所以,并发编程中,只靠volatile修饰共享变量是不可靠的,最终还是要通过对关键方法加锁来保证线程安全
就如上面的demo,稍加修改就能实现真正的线程安全
最简单的,给increase方法加个synchronized (synchronized怎么实现线程安全的我就不啰嗦了,我以前讲过 synchronized底层实现原理)
private synchronized static void increase() {
++count;
}
run几下
这不就妥了嘛
到现在,对于以下两点你应该有了新的认知
volatile保证变量在内存中对线程的可见性
volatile只保证可见性,不保证原子性
关于指令重排
并发编程中,cpu自身和虚拟机为了提高执行效率,都会采用指令重排(在保证不影响结果的前提下,将某些代码乱序执行)
关于cpu:为了从分利用cpu,实际执行指令时会做优化;
关于虚拟机:在HotSpot vm中,为了提升执行效率,JIT(即时编译)模式也会做指令优化
指令重排在大部分场景下确实能提升执行效率,但有些场景对代码执行顺序是强依赖的,此时我们需要禁用指令重排,如下面这个场景
伪代码取自《深入理解Java虚拟机》:
其描述的场景是开发中常见配置读取过程,只是我们在处理配置文件时一般不会出现并发,所以没有察觉这会有问题。
试想一下,如果定义initialized变量时没有使用volatile修饰,就可能会由于指令重排序的优化,导致位于线程A中最后一条代码“initialized=true”被提前执行(这里虽然使用Java作为伪代码,但所指的重排序优化是机器级的优化操作,提前执行是指这条语句对应的汇编代码被提前执行),这样在线程B中使用配置信息的代码就可能出现错误,而volatile通过禁止指令重排则可以避免此类情况发生
禁用指令重排只需要将变量声明为volatile,是不是很神奇
我们来看看volatile是如何实现禁用指令重排的
也借用《深入理解Java虚拟机》的一个例子吧,比较好理解
这是个单例模式的实现,下面是它的部分字节码,红框中 mov%eax,0x150(%esi) 是对instance赋值
可以看到,在赋值后,还执行了 lock addl$0x0,(%esp) 指令,关键点就在这儿,这行指令相当于此处设置了个 内存屏障 ,有了内存屏障后,cpu或虚拟机在指令重排时就不能把内存屏障后面的指令提前到内存屏障前面,好好捋一下这段话
最后,留一个能加深大家对volatile理解的问题,兄弟们好好思考下:
Java代码明明是从上往下依次执行,为什么会出现指令重排这个问题?
|
最新内容
相关内容
Python之关于高效使用字典的清单
Python之关于高效使用字典的清单,代码,数据,字典,培训,扩大,时报,方式,方法,对象,列表,字典(dict)对象是Python最常用的数据结构,社区曾有人开提升Python程序性能的好习惯
提升Python程序性能的好习惯,代码,名字,检测,数据,培训,模块,函数,变量,性能,对象,掌握一些技巧,可尽量提高Python程序性能,也可以避免不必要的数据科学中必须了解的Python核心库
数据科学中必须了解的Python核心库,数据,生产,代码,标准,分析,培训,图片,工具,统一,涉足,python有三个核心数据科学库,在此基础上还创建了许多Python继承性和 java 是一样的吗?
Python继承性和 java 是一样的吗?,情况,代码,名称,第一,培训,初始化,函数,方法,定义,变量,聊完了多态,再来看看面向对象的另一个特征:继承性。关于Python 的 Web 自动化测试的实
关于Python 的 Web 自动化测试的实践,代码,分析,环境,系统,软件,信息,测试,工作,较大,百度,Web测试是软件测试中比较的一个分支,而要实现Web自Python查看模块中的属性
Python查看模块中的属性,名称,标准,环境,培训,属性,模块,对象,函数,定义,变量,内置函数dir可用于列出某模块中定义了哪些属性(全局名称空间)Python性能分析
Python性能分析,代码,分析,对比,时间,工具,标准,注释,跨行,通用,报告,python标准库提供两个代码性能分析相关的模块,即timeit和cProfile/profiPython 类的私有属性和方法
Python 类的私有属性和方法,私有化,培训,开头,变量,函数,模块,属性,目的,方法,更多,在Python中,通过单下划线”_”来实现模块级别的私有化,一般python 高级特性
python 高级特性,代码,工具,适当,第一,培训,流程,函数,语句,索引,元素,在Python中,代码不是越多越好,而是越少越好。代码不是越复杂越好,而是越python如何看变量属性
python如何看变量属性,培训,属性,函数,变量,参数,对象,方法,列表,范围内,字典,1、使用dir()函数查看dir()函数不带参数时,返回当前范围内的变python如何打印一个对象的全部属性
python如何打印一个对象的全部属性,代码,公用,情况,培训,属性,对象,字典,全部,方法,动态,python打印对象的全部属性,可以使用__dict__方法。遇python怎么打包整个项目
python怎么打包整个项目,环境,标准,项目,培训,工作,工具,脚本,状况,以上,版本,distutils-Python自带的基本安装工具,适用于非常简单的应用场