在说这个计算机术语之前,我先在这里问候所有问“什么是JVM伪共享”的垃圾JAVA程序员以及一瓶不满半瓶晃荡的面试官全家
我从来没想过国内已经很卷的JAVA圈,已经卷到语无伦次的地步了,“伪共享”是java程序员应该知道的吗?能问出这个问题的人,除了炫技(不知道从哪个网站得知的概念),这就好比我问一个UI,photoshop是如何实现的一样,在我看来,他会觉得我很NB,但是在他的眼中,我和SB有什么区别
吐槽完毕,伪共享,指的是一种现象,如果出现了这种现象,那就叫做伪共享,我以mysql的一个表做示例,相信你很快明白,下面是一个很简单的数据库表
假设现在我开启2个事务
事务1:修改id=1这条数据的name
事务2:修改id=1这条数据的age
如果我们不知道锁的颗粒度为行锁的情况下,我们会以为:
虽然是同一行,但是字段不同,事务1和事务2完全不冲突,那么它们应该并发的进行修改
很明显,我们上述的想法(以为)是错误的,虽然name和age是不同的字段,但是它们是一行数据,由于锁的最小粒度是行级,所以,要么事务1执行完之后,再执行事务2,或者事务2执行之后再执行事务1,总之,它俩不会一起执行,总要有个先后
我们上述的想法中的现象,就是伪共享,也就是“事务1和事务2完全不冲突”这个现象,就是伪共享,说白了就是这句话是错误的,我们以为可以同时修改,但是他们不会同时修改,这种现象,就是伪共享
伪共享现象的存在,导致可以运行的更快的程序,无法运行的更快,下面的方式可以解决伪共享,对于本例来说
建立2个表,一个name表,专门存name,一个age表,专门存age,这样的话事务1和事务2相互之间没有冲突
文章到此为止,你应该知道什么叫做伪共享,以及如何解决伪共享,下面说一个新的概念:cacheline
CPU与内存之间速度有巨大差异,为了减少这个差异,在CPU和内存之间,会有多个叫cacheline的缓存,多核CPU,多个核共享cacheline
将上述的伪共享例子带入实际情况,如下:
1:上述示例中的【数据库表】,指的就是cacheline
2:上述示例中的【事务1】和【事务2】,指的是多核CPU的多个核
3:上述示例中的name和age指的是内存中的数据
假设CPU有2个核,分别是核1和核2,并且按照如下先后顺序执行,此处涉及到局部性原理,不过问题不大
1:核1读取name数据,先判断的cacheline中是否有name,发现没有,则去内存中读取name,因为我们定义变量的时候,name和age是挨着的,所以顺便也把age读出来了(局部性原理),然后将name和age放到cacheline X中
2:核2读取age数据,先判断cacheline中是否有age,发现cacheline X中存在age(核1顺手带过来的),此时则不会再去内存中读取
此时,核1修改cacheline X中的name,并且再修改的过程中,核2修改的cacheline X中的age,这个时候,伪共享现象出现,因为CPU为了保证数据一致,一个核修改一个cacheline的时候,其他核心是不允许修改这个cacheline的
解决伪共享:核1读取name的时候,不要顺手把age放到cacheline X中,但是考虑到局部性原理,读取是成块操作的,只有一个办法,那就是让name和age分开,读取name的时候,name附近的那块数据里没有age即可,这样的话核1读取name的时候,会把name缓存到cacheline X中,当核2读取age的时候,很大可能将age缓存到cachelien Y中,这样,name和age就不在一个cacheline中了,之后核1与核2可并发修改name和age
如何将name和age分开:非常简单,将name和age两个属性分开,中间使用其他属性,例如sex什么的即可,或者,name和age两个属性不必分开,但是要保持足够宽,为了保证足够宽,可将name后面用0填充,或者在age前面用0填充内存,确保它俩占用两个cacheline
Java使用的是0填充的方式,以空间换取速度,在java中,0填充使用的是long类型,不过无所谓,主要目的就是0填充