2016 - 2024

感恩一路有你

数据库内核开发demo java编程,如何彻底理解volatile关键字?

浏览量:2143 时间:2023-05-24 08:49:49 作者:采采

java编程,如何彻底理解volatile关键字?

谢谢了邀请~!下面从用法、注意事项、底层原理并且说明!

JMM基础-计算机原理

Java内存模型即Java Memory Model,西安北方光电有限公司JMM,JMM符号表示了Java虚拟机(JVM)在计算机(RAM)中的工作。JVM是整个计算机虚拟充值模型,所以我JMM是直属中央于JVM的,

在计算机系统中,寄存器是L0级缓存,随即顺次排列是L1,L2,L3(这一次是内存,本地磁盘,远战存储).越往上的缓存存储空间越小,速度越快,成本也越高。越往上的存储空间越大,速度更慢,成本也越低。

从上至下,每一层都都也可以是看作是更下一层的缓存,即:L0寄存器是L1一级缓存的缓存,

L1是L2的缓存,两次中间数;每一层的数据是来当然了它的下一层。

在现在CPU上,一般来说L0,L1,L2,L3都无法继承在CPU内部,而L1还可分一级数据缓存和一级指令缓存,三个用于贮放数据和执行数据的指令解码,每个核心拥有的的的运算处理单元、控制器、寄存器、L1缓存、L2缓存,然后再一个CPU的多个核心共享后来一层CPU缓存L3。

CPU的缓存一致性解决方案

两类200元以内两种方案

总线锁(每次锁总线,是悲观锁)

缓存锁(只锁缓存的数据)

MESI协议::

M(modify):I(invalid)E(Exclusive)S(manage)

JMM内存模型的八种网络同步操作

1、read(加载),从主内存读取数据

2、load(写入):将主内存读取数据到的数据写入到到工作内存

3、use(不使用):从工作内存读取数据来可以计算

4、assign(变量):将计算出好的值新的变量赋值到工作内存中

5、store(存储):将工作内存数据中写入主内存

6、write(写入):将store过去的变量值定义变量给主内存中的变量

7、lock(移动到):将主内存变量加锁,标志为线程雀占鸠巢状态

8、unlock(解锁):将主内存变量解锁,解锁后其他线程可以不移动到该变量

Java内存模型带来的问题

1、要知道性问题

左边CPU中不运行的线程从主内存中拷备对象params到它的CPU缓存,把对象params的count变量转成2,但这个变更对运行程序在右边的CPU中的线程是万不可见,因为这个改还没有flush到主内存中。

在多线程环境下,如果某个线程唯一一个加载链接共享变量,则是需要到主内存中声望兑换该变量,后再卡内到工作内存中,以后只不需要在工作内存中读取该变量再试一下,同时假如对该变量先执行了修改的操作,则先将新值写入到工作内存中,然后把再重新登陆至于内存中,只不过什么时候哪个网站的值会被重新登陆到主内存中是不太判断的,一般来说是迅速的,不过具体看时间未知,,要解决宽带共享对象而且性问题,我们也可以可以使用volatile关键字的或加锁。

2、竞争问题

线程A和线程B共享一个对象params,打比方线程A从主存加载变量到自己的缓存中,同时,线程B也读取文件了变量到它的CPU缓存,另外这两个线程都对做了加1操作,此时,加1操作被不能执行了两次,当然了都在相同的CPU缓存中。

要是则四个加1操作是并行接口负责执行的,这样的话变量便会在各种值上加2,到最后主内存中的值会为3,接着图中两个加1操作是分头并进的,论是线程A肯定线程B先flush计算出而到主存,终于主存中的只会增加1次转成2,哪怕总共有两次加1你的操作,要帮忙解决上面的问题我们是可以在用synchronized代码块。

3、重升序

除此之外宽带共享内存和工作内存给了的问题,还必然重排序的问题,在执行程序时,目的是想提高性能,编译器和处理器经常会会对指令做重排序。

重排序分3中类型:

(1)编译器优化的重排序。

(2)指令级并行的重排序

(3)内存系统的重排序

①数据依赖性

数据依赖性:如果不是两个操作不能访问同一变量,且这两个操作中有一个为写,此时这两个操作之间就修真者的存在数据依赖性。

依赖性可分100元以内三种:

上图很的确,A和C存在数据依赖,B和C也存在地数据依赖,而A和B之间不未知数据依赖,如果没有重排序了A和C也可以B和C的执行顺序,程序的执行结果是会被变动。

很的确,论如何重排序,都可以只要代码在单线程下的运行错误的,连单线程下都难以只要,更不用什么再讨论多线程并发的情况,所以才就做出一个and-if-serial的概念。

4、like-if-serial

意思是:论怎么重排序(编译器和处理器是为想提高并行度),(单线程)程序的执行结果又不能被决定。编译器、runtime和处理器都前提是不违背as-if-serial语义。

A和C之间未知数据依赖,同样的B和C之间也存在地数据依恋关系,而在结果想执行的指令序列中,C不能被重排序A和B的前面(C排到A和B的前面,程序的结果将是被决定)。但A和B之间没有数据依恋关系,编译器和处理器也可以重排序A和B之间的想执行顺序。

like-if-serial语义把单线程程序保卫了下来,遵守as-if-serial语义的编译器、runtime和处理器可以让我们感觉到:单线程程序感觉起来是按程序的顺序来想执行的。as-if-srial语义使单线程程序不需担心重排序干扰到他们,也不需要担心那内存可见性的问题。

5、内存屏障

Java编译器在生成指令序列的适度地位置会插入内存屏障来私自特定的事件类型的处理去重排序,最终达到让程序按我们想象之外的流程去负责执行。

①保证某种特定操作的执行顺序

②会影响某些数据(或者是某条指令的执行结果)的内存可见性

编译器和CPU也能重排序指令,可以保证到最后同一的结果,接触优化系统性能。插入到一条MemoryBarrier会说说编译器和CPU,不管什么指令都又不能和这条Memory Barrier指令重排序。

MemoryBarrier所做的另外一件事是满刷出各种CPUcache,如一个Write-Barrier(写入到屏障)将刷出处的Barrier之前写入文件cache的数据,因此,任何CPU上的线程都能读取到这些数据的2011版版本。

JMM把内存屏障指令统称4类:

StoreLoadBarriers是一个全能型的屏障,它同样的具备其他3个屏障的效果,

volatile关键字可以介绍

1、绝对的保证要知道性

对一个volatile变量的读,时总能看见(不可以线程)对这个volatile变量到最后的写。

我们先看下面代码:

initFlag还没有用volatile关键字形容词性;

上面结果为:

那就证明一个线程变化initFlag状态,别外一个线程看不到;

如果而且volatile关键字呢?

而追加:

我们通过汇编看下代码的终于底层实现程序:

volatile写的内存语义追加:

当写一个volatile变量时,JMM会把该线程按的本地内存中的共享变量值重新登录到主内存。

当读一个volatile变量时,JMM会把该线程不对应的本地内存置为生效。线程接下来的将从主内存中无法读取链接共享变量。

比如说:

如果没有我们将flag变量以volatile关键字可以修饰,这样的话事实上:线程A在写flag变量后,本地内存A中被线程A更新过的两个宽带共享变量的值都被手动刷新到主内存中。

在读flag变量后,本地内存B包含的值巳经被置为不能解除。此时,线程B可以从主内存中读取文件互相访问变量。线程B的读取操作将可能导致本地内存B与主内存中的共享变量的值变的一致。

如果没有我们把volatile写和volatile读两个步骤看专业出声看的话,在读线程B读一个volatile变量后,写线程A在写这个volatile变量之前所有而且的共享变量的值都将立即变得异常对读线程B可以说。

2、原子性

volatile不能保证变量的原子性;

运行结果万分感谢:

只不过count

包涵三个操作:

(1)读取变量count

(2)将count变量的值加1

(3)将计算出后的值再赋给变量count

从JMM内存总结:

下面从字节码分析为什么i这种的用volatile修改不能能保证原子性?

javap:字节码查找

不过i这种操作主要可以分成三类3步:(汇编)

加载volatile变量值到local提高变量的值把local的值写回,让其它的线程所以说

Load到store到内存屏障,最少4步,其中到最后半步jvm让这个2012版的变量的值在所有线程可见,也就是之后踏上一步让所有的CPU内核都额外了哪个网站的值,但中间的几步(从Load到Store)是不安全的的,中间要是其他的CPU可以修改了值将会全部丢失。

3、活动有序性

(1)volatile重排序规则表

①当第二个你操作是volatile写时,反正第一个操作是什么,都肯定不能重排序。这个规则确保volatile写之前的操作不会被编译器重排序到volatile写之后。

②当最后一个操作是volatile读时,无论第二个操作是什么,都不能重排序。这个规则以保证volatile读之后的操作应该不会被编译器重排序到volatile读之前。

③当另一个能操作是volatile写,第二个操作是volatile读时,又不能重排序。

(2)volatile的内存屏障

①volatile写

storestore屏障:这对这样的语句store1storestorestore2,在store2及后续写入操作不能执行前,保证store1的中写入操作对其它处理器所以说。(也就是说如果再次出现storestore屏障,那么store1指令一定会会在store2之前不能执行,CPU不会store1与store2并且重排序)

storeload屏障:对于这样的语句store1storeloadload2,在load2及现所有加载操作不能执行前,保证store1的写入文件对所有处理器而且。(也就是说假如又出现storeload屏障,这样的话store1指令是有会在load2之前想执行,CPU不会对store1与load2接受重排序

②volatile读

在每个volatile读操作的后面插入一个LoadLoad屏障。在每个volatile读操作的后面直接插入一个loadstore屏障。

loadload屏障:对于这样的语句load1loadloadload2,在load2及后续读取操作要无法读取的数据被不能访问前,可以保证load1要读取数据的数据被加载后。(也就是说,要是再次出现loadload屏障,那你load1指令是有会在load2之前执行,CPU不会对load1与load2进行重排序)

loadstore屏障:是对这样的语句load1loadstorestore2,在store2及后续写入操作被刷出前,能保证load1要读取的数据被读取数据完毕后。(也就是说,如果没有再次出现loadstore屏障,那就load1指令一定会在store2之前不能执行,CPU应该不会对load1与store2并且重排序)

volatile的实现原理

volatile的实现原理

?通过对OpenJDK中的unsafe.cpp源码的分析,会发现被unsafe关键字修饰的变量会存在一个“lock:”的前缀。

?Lock前缀,Lock不是一种内存屏障,但是它能结束的的内存屏障的功能。Lock会对CPU总线和高速缓存加锁,这个可以表述为CPU指令级的一种锁。

?同样的该指令会将当前处理器缓存行的数据然后写会到系统内存中,且这个写回内存的操作会使在其他CPU里系统缓存了该地址的数据不能解除。

?具体一点的执行上,它先对总线和缓存加锁,然后再想执行后面的指令,结果能量锁后会把高速缓存中的脏数据完全刷新回主内存。在Lock锁住总线的时候,其他CPU的读写请求都会被阻塞,直到锁释放。

【欢迎随手查哈@码农的几天,希望对你有帮助】

如何学好物联网的知识?

来看看这里,这里有你打算的物联网中核心嵌入式系统的课程。

我准备着了跑一趟树莓派之旅,建议使用jupyter-notebook通过边学边练(受李沐老师《动手学深度学习》课程启发),绝对不能出现树莓派吃灰。

当前使用树莓派3B和树莓配瑞士军刀扩展板卡进行树莓派由外而内的学习(想要生级为树莓派4B板卡,后续课程会兼容性问题树莓派3B和4B),多谢了来围观默默点赞。

本课程可以解决树莓派不使用2大难题:

(1)树莓派系统软件安装的复杂性(Linux字符界面是需要一些时间不适应)。

你不用什么安装好其他软件,可以使用我可以提供的系统镜像即可开始怎么学习,镜像中另外中有教程和源码。

(2)树莓派只是因为很简单跑跑卡丁被人的DEMO,接着就没后再了。

我会手下各位朋友,由外而因的探索树莓派,从PYHON篇就开始、历经C语言篇、Linux内核驱动篇、Linux内核核心篇再继续树莓派的“下手学”系列课程。同时,会在树莓派上不运行深度学习目标检测检测中的yolo模型,试验树莓派运行和movidius2加速棒的差异,做一个有工程意义的项目。

?

本套课程包涵教程、源码、视频教程所有免费提供。

(1)课程视频:

《树莓派系统安装说明》:

《动手学树莓派——python上篇》:

?

(2)教程提供的系统镜像:

系统镜像下载地址:

?

(3)课程介绍和课程资源汇总:

gitee托管地址:_taste_raspi/shirf_serial_

github托管地址:_serial_

?

(4)课件:

《树莓派系统安装篇》:

gitee托管地址:_taste_raspi/raspi_os_setup

github托管地址:_os_

?

《动手学树莓派——python上篇》:

gitee托管地址:_taste_raspi/perfect_raspi_python_

github托管地址:_raspi_python_

内存 变量 CPU 数据 屏障

版权声明:本文内容由互联网用户自发贡献,本站不承担相关法律责任.如有侵权/违法内容,本站将立刻删除。