其他分享
首页 > 其他分享> > Volatile

Volatile

作者:互联网

volatile解决的问题:

解决了程序的可见性禁止了指令的重排序

可见性的原因

为了解决cpu每次读取都是从主内存中读取数据 cpu进行了一次优化 增加了高速缓存(可以理解成redis 只是维度不同) 但是这也直接的导致了 缓存不一致的问题

解决高速缓存的方案

增加了缓存锁和总线锁
总线锁:
相当于在缓存行和主内存中间加了一个锁 但是效率极低 每次只能让一个cpu执行
缓存锁
就是指内存区域如果被缓存在处理器的缓存行中,并且在Lock期间被锁定,那么当它执行锁操作回写到内存时,不再总线上加锁,而是修改内部的内存地址,基于缓存一致性协议来保证操作的原子性。

自己的理解:
比如CPU0 去修改了count =1的值 他会发送一个嗅探协议给CPU1 告诉CPU1 此期间不能进行读取数据等操作并让cpu1的缓存行失效CPU1返回给CPU0我知道了之后CPU0会把数据放到主内存 然后由于CPU1的缓存行失效了所以需要从主内存中读取 这样解决了缓存一致性问题
在这里插入图片描述

Snoopy(嗅探协议)

监听总线的事件 其他cpu接受事件对相应缓存的处理

缓存一致性协议( 最常见MES)

  1. M(Modify) 表示共享数据只缓存在当前CPU缓存中,并且是被修改状态,也就是缓存的数据和主内
    存中的数据不一致\ (对数据进行修改 修改之后就变成了Modify)
  2. E(Exclusive) 表示缓存的独占状态,数据只缓存在当前CPU缓存中 不存在其他cpu核心,并且没有被修改
  3. S(Shared) 表示数据可能被多个CPU缓存,并且各个缓存中的数据和主内存数据一致 共享状态情况下需要去发送消让其他缓存行失效
  4. I(Invalid) 表示缓存已经失效 当cpu0修改之后 cpu0会发送一个lock 指令让所有共享的缓存行失效 进入独占状态

Store Buffer

因为Cpu0 给cpu1 发送消息 到cpu1 返回消息给cpu0 这个过程会有一个阻塞的状态这样会影响性能 所以CPU又进行了进一步的优化 引入了 Store Buffer

就相当于先把 数据放到 Store Buffer 然后在从 Store Buffer 放到缓存行 然后同步到主内存 这样的话可以异步的执行 但是又会出现指令重排序的问题

解决指令重排序问题的方法

引入内存屏障
1.Store Memory Barrier(写屏障) ,告诉处理器在写屏障之前的所有已经存储在存储缓存(storebufferes)中的数据同步到主内存,简单来说就是使得写屏障之前的指令的结果对屏障之后的读或
者写是可见的 (写屏障的指令必须在写屏障之前完成)
2.Load Memory Barrier(读屏障) ,处理器在读屏障之后的读操作,都在读屏障之后执行。配合写障,使得写屏障之前的内存更新对于读屏障之后的读操作是可见的 (在这个屏障之前的的读操作必须在这个屏障之前完全)
3.Full Memory Barrier(全屏障) ,确保屏障前的内存读写操作的结果提交到内存之后,再执行屏障后的读写操作 (读写屏障必须在屏障执行之前执行)

JMM

MM定义了共享内存中多线程程序读写操作的行为规范:在虚拟机中把共享变量存储到内存以及从内存中取出共享变量的底层实现细节。通过这些规则来规范对内存的读写操作从而保证指令的正确性,它解决了CPU多级缓存、处理器优化、指令重排序导致的内存访问问题,保证了并发场景下的可
见性。
JMM

java内存模型规定所有变量都存在主内存中 每条线程都有自己的工作内存 ,线程的工作内存中 保存了这个线程中用到的 变量的主内存副本拷贝 线程对变量的操作必须在自己的工作内存中进行而不是直接操作主内存
jmm 保证了java程序在各种平台的运行情况下 对内存的访问都能实现可见性

Happens-Before模型(不会出现可见性问题)

1.程序顺序规则(as-if-serial语义)
一个线程的任意操作 Happens-Before 于该线程中任意后续操作
不能改变程序的执行结果(在单线程环境下,执行的结果不变.)
依赖问题, 如果两个指令存在依赖关系,是不允许重排序
2. Volatile 变量规则
volatile 修饰的变量的写操作,一定happens-before后续对于volatile变量的读操作.
在这里插入图片描述
3. 监视器(加锁)规则
对一个锁的解锁 Happens-Before 于随后对这个锁的加锁
int count ==10;
synchronized(this){
// count ==10
}

4.start规则
int a = 0;
Thread t1 = new Thread ()
a=20;
t1.start()
输出结果 20 在start之前赋值 都会被写进内存
5.join()规则
join :阻塞当前线程
让线程执行结果对后续线程可见

DCL问题

在这里插入图片描述

缓存行

cpu的缓存是由多个缓存行组成
cpu每次读取数据的时候他会加载一段数据到缓存行中
缓存行是内存和cpu交互的最小工作单元

对其填充(对不足64个字节的类进行手动填充)

需要保证一个类在64个字节或以上

伪共享

比如 当缓存行没有达到64位的时候在多线程情况下 因为缓存一致性 CPU0 和CPU1同时去加载缓存行中的数据 CPU0去加载X CPU1去加载Y 如果CPU0加载成功 那么CPU1就不会在加载成功直接读取内存 这样就出现了伪共享的问题

伪共享解决方法

@Contended 注解加在类上 表示这个类必须填充完64个字节
如果要让注解生效需要加jvm参数 -XX:-RestrictContended

final域

构造方法的执行 不允许重排序到构造方法之外

标签:缓存,CPU1,屏障,线程,内存,Volatile,cpu
来源: https://blog.csdn.net/qq_41956309/article/details/117190484