1.主要作用:用在多线程编程时,当在jvm开启优化性能的时候,强制线程在使用volidate修饰的变量的时候,从主内存中读取,不从线程副本内存中读取变量,从而保证了变量的同步安全。
2.为什么会有线程副本?
因为这个是jvm开启优化的一种方式。跟jvm内存分配有关。
简要分析:
有一个主内存,和运行线程复制主内存副本的内存。
当一个线程运行的时候,会把用的变量从主内存中读取到自己线程内存,在线程运行过程中操作变更的值都在本地内存存储,最后,再把线程内存中的值回写到主内存中。
这样,当别的线程运行时,也是从主内存中加载变量(其他线程还未写回写主内存),就会造成线程拿到的数据不是最新的,出现数据混乱。
当用volidate修饰变量的时候,就会强制所有线程每次读取变量,都从主内存中读取,并且变化直接操作主内存,不在本地线程内存。
但是用volidate修饰,并不能保证变量操作的原子性,即不能保证一个线程执行该变量时的原子操作,会被其他线程中断,引起数据不一致。
volidate是多线程环境下,可以保证不同的线程之间,操作同一个变量能互相通讯可见的。但是不能保证操作原子性。
volidate是如何保证内存可见性的/有序性,防止指令重排序?
主要是内存屏障,通过添加指令保证
可见性
1.通过指令。lock前缀指令。在由volidate修饰的变量,前后会加上lock指令,在计算操作完变量后,会强制变量直接写会主内存。
然后,通过协议嗅探机制线程的工作内存,通知其他的线程从主内存读取数据。
volidate修饰的变量,会在程序执行前后添加一个汇编指令,这个指令会促发相应的协议,这个协议会促发通过嗅探技术让总线不同的监控变量值的变化,,一旦变化,会直接从主内存写回工作内存。
有序性
内存屏障指令,load,loadstore等指令 loadBarrier 读屏障 ,storeBarrier写屏障。在前后代码加上这些指令,可以保证代码执行指令不进行重排序。
cpu和内存交互
cpu缓存
内存读取的速度,赶不上cpu的速度,所以在cpu上加了高速缓存。
但是这样就有了缓存一致性的问题?每个cpu都有自己的高速缓存区。这样对于涉及一块主内存区域的操作的时候,怎么保证各个缓存区的数据一致性,
有缓存,就会有一致性问题
为了解决这个问题,各个cpu需要遵循一致性协议MSI,MESI
在cpu层面,提供了一系列的保障
1.硬件层内存屏障 loadBarrier 读屏障 ,storeBarrier写屏障
不同硬件处理器的内存屏障方式不一样,java怎么屏蔽这些差异的:通过jvm生成内存屏障指令。
读指令;在其他指令前插入读指令,会直接读取主内存的数据,使得高速缓存区失效 volidate就是这样的。在读写前后添加内存屏障,实现内存可见性
内存屏障的作用:
强制从主内存读取数据,使得脏数据被刷新,重写写回主内存, 阻止内存屏障两侧指令重排序
编写并发程序,主要是对于共享变量的管理
什么是同步:
同步不仅是原子操作,临界。更是内存可见性的。一个线程同步操作完,希望操作后的信息,被其他线程看到。
什么是重排序
重排序:会导致写入变量的顺序和代码的顺序不一致。在多线程环境里面。不能保证,按照程序写定的顺序执行
volidate修饰,可以使变量的值,在一个线程更新时,其他线程可以预见,不会重排序。这个变量会被监视是共享的
volidate的使用:
在一个引用的状态需要可见,或则标志重要的生命周期事件(初始化和销毁。)检查状态的标记:循环
volidatae只能保证可见性,不能保证原子性。 加锁:既可以保证原子性和可见性。 对于变量修改用volidate不能达到数据同步安全
使用volidate条件:
当只有一个线程修改变量操作,
写入的变量不依赖其他线程的变量值
变量不需要和其他需要变更状态的变量之间有依赖。
访问变量时,没有其他原因加锁