假设一台计算机配备两个CPU,在每个CPU中,cache 按地址被分成了两块 cache banks,分别是cache bank0 和 cache bank1。 举个理想的内存访问指令顺序例子: 1,CPU0往cache address 0×12345000 写入一个数字 1。因为address 0×12345000是偶数,所以值被写入 bank0. 2,CPU1读取 bank0 address 0×12345000 的值,即数字1。 3,CPU0往 cache 地址 0×12345100 写入一个数字 2。因为address 0×12345100是奇数,所以值被写入 bank1. 4,CPU1读取 bank1 address 0×12345100 的值,即数字2。 重排序后的内存访问指令顺序: 1,CPU0 准备往 bank0 address 0×12345000 写入数字 1。 2,CPU0检查 bank0 的可用性。发现 bank0 处于 busy 状态。 3,CPU0 为了防止 cache等待,发挥最大效能,将内存访问指令重排序。即先执行后面的 bank1 address 0×12345100 数字2的写入请求。 4,CPU0检查 bank1 可用性,发现bank1处于 idle 状态。 5,CPU0 将数字2写入 bank 1 address 0×12345100。 6,CPU1来读取 0×12345000,未读到 数字1,出错。 7,CPU0 继续检查 bank0 的可用性,发现这次bank0 可用了,然后将数字1写入 0×12345000。 8,CPU1 读取 0×12345100,读到数字2,正确。 可以看到第 3 步发生了指令重排序,并导致第 6步读到错误的数据。通过对指令重排,CPU可以获得更快地响应速度。重排序是为了优化性能,但是不管怎么重排序,单线程下程序的执行结果不能被改变。 内存屏障是用来防止CPU出现指令重排序的利器之一,一般在Java中,我们可以使用volatile来修饰变量,volatile底层就是使用到了内存屏障。 内存屏障会提供3个功能: 1.在执行到内存屏障这句指令时,在它前面的操作已经全部完成; 2.它会强制将对缓存的修改操作立即写入主存; 3. 如果是写操作,它会导致其他CPU中对应的缓存行无效。
点赞 评论
牛客网
牛客企业服务