最近看《Java并发编程之美》,知识网经过重新梳理,清晰了很多,比如这里的伪共享问题就涉及到了操作系统的局部性原理。
1、内存模型
简单来说,Java内存模型是Java虚拟机规范中试图定义的一种屏蔽各个硬件平台和操作系统的内存访问差异的模型。
从多线程的角度,可以把内存模型简化为:主内存和线程本地内存,线程可以把变量从主内存读取到本地内存中,然后在本地内存中读写,然后将改变结果写入到内存中。
通常来说,现在都是多核CPU多线程,CPU的物理核数对应同时可以并行的线程数量(超线程技术使得实际并行线程数通常是物理核数的两倍)。如下显示了双核CPU的工作模型。
2、伪共享
为了解决主内存和CPU之间运行速度的差问题,会在CPU和主内存之间添加一级或多级高速缓存,这些cache一般被集成在CPU内部,且在cache内部是按行存储的。
当CPU访问某个变量时,首先查看CPU cache中是否有该变量,如果有直接获取,否则就去主内存获取,然后把该变量所在的内存区域的一个行大小的内存复制到cache中。——缓存与内存交换的数据单位就是缓存行
所以一个缓存行中可能存有多个变量,当多个线程同时修改一个缓存行里面的多个变量时,由于同时只能有一个线程操作缓存行,所以相比将每个变量放到一个缓存行,性能会有所下降,这就是伪共享。
2.1 局部性原理:
空间局部性:一旦一块存储单元被访问,那么他附近的存储单元也可能被访问
时间局部性:一旦一块存储单元被访问,那么在接下来时间,它也更可能被访问
2.2 产生的原因
伪共享产生是因为多个变量被放入同一个缓存行中,并且多个线程同时去写入缓存行中不同的变量。
而这种局部性原理的应用,在单线程时可以加速程序运行,而在多线程并发修改一个缓存行的多个变量时,就会竞争缓存行。书中举了一个例子,通过双层循环往二维数组中写入值,按行写入时比按列写入时要快,也侧面印证了单线程中局部性原理的优化作用。
2.3 避免伪共享
JDK8之前通过字节填充的方式来避免该问题,也就是创建一个变量时使用填充字段填充该变量所在的缓存行,这样就避免了多个变量存放在同一个缓存行。
JDK8提供了一个sun.misc.Contented注解来解决伪共享问题。