关键字volatile在Java中本质上是一种修饰符,它用来修饰变量。
volatile关键字在多线程编程中非常重要,它保证了多个线程之间变量的可见性和有序性。
volatile修饰变量的特性
可见性
被volatile修饰的变量对于所有线程都是可见的,即当一个线程修改volatile变量的值后,其他线程立即可见修改后的值。写完后立即刷新回主内存并及时发出通知,大家可以去主内存拿最新版,前面的修改对后面所有线程可见,
1. public class interruptDemo { 2. static volatile boolean isStop = false; 3. 4. public static void main(String[] args) { 5. new Thread(() -> { 6. while (true) { 7. if (isStop) { 8. System.out.println(Thread.currentThread().getName() + "\t isStop被修改为true,程序停止"); 9. break; 10. } 11. System.out.println("t1 is come in ..."); 12. } 13. 14. }, "t1").start(); 15. try { TimeUnit.MICROSECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } 16. new Thread(() -> { 17. isStop = true; 18. }, "t2").start(); 19. 20. 21. } 22. }
有序性(禁止指令重排序)
VM会对代码进行优化,其中包括指令重排序,如果一个操作的结果并不影响程序的正确性,那么JVM很有可能会将此操作与其后面的操作交换执行顺序。而在多线程环境中,这种指令重排序可能会导致程序输出不正确的结果,因此使用volatile关键字可以禁止指令重排序优化。
重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段,有时候会改变程序语句的先后顺序
不保证原子性
volatile修饰的变量仅仅保证了可见性和禁止指令重排序优化,但不保证它的操作具有原子性。对于需要具备原子性的操作,需要使用synchronized关键字或者使用Java.util.concurrent.atomic包中的原子类。
volatile的内存语义
当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值立即刷新回主内存中。.
当读一个volatile变量时,JMM会把该线程对应的本地内存设置为无效,重新回到主内存中读取最新共享变量
所以volatile的写内存语义是直接刷新到主内存中,读的内存语义是直接从主内存中读取。
内存屏障
在Java中,内存屏障是一种机制,用于保障程序在多线程环境中的数据一致性。它可以保证指令的执行顺序,避免出现线程安全问题。
内存屏障(也称内存栅栏,屏障指令等,是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作),避免代码重排序。内存屏障其实就是一种JVM指令,Java内存模型的重排规则会要求Java编译器在生成JVM指令时插入特定的内存屏障指令,通过这些内存屏障指令,volatile实现了Java内存模型中的可见性和有序性(禁重排)
内存屏障之前的所有写操作都要回写到主内存。
内存屏障之后的所有读操作都能获得内存屏障之前的所有写操作的最新结果(实现了可见性)。
内存屏障分类
第一种分法:
1. Load Barrier(读屏障):确保前面的读操作必须在它之前的写操作完成之后才执行。
2. Store Barrier(写屏障):确保后面的写操作必须在它之前的读操作完成之后才执行。
3. Full Fence(全屏障):保证所有前面的操作都完成后再执行后面的操作。
4. StoreLoad Barrier(写-读屏障):保证后面的读操作必须在前面的写操作完成后执行。
第二种分法:
第三种粗分:
读屏障(LoadBarrier):在读指令之前插入读屏障,让工作内存或CPU高速缓存当中的缓存数据失效,重新回到主内存中获取最新数据
写屏障(StoreBarrier):在写指令之后插入写屏障,强制把写缓冲区的数据刷回到主内存中
读屏障
在每个volatile读操作的后面插入一个LoadLoad屏障
在每个volatile读操作的后面插入一个LoadStore屏障
写屏障
在每个volatile写操作的前面插入一个StoreStore屏障
在每个volatile写操作的后面插入一个StoreLoad屏障
禁重排写指令
禁重排读指令
凭什么我们java写了一个volatile关键字系统底层加入内存屏障?两者关系怎么勾搭上的?
Java中的volatile关键字会告诉Java虚拟机,在访问该变量时总是从主内存中读取数据,在修改该变量时总是将数据写回主内存中。这个特性可以保证在多线程环境下,不同线程访问该变量时不会出现数据不一致的问题。
为了实现这个特性,Java虚拟机会在volatile变量的读写操作前后加上内存屏障(Memory Barriers),它会强制将写缓冲区/高速缓存中的数据刷新到主内存中,或强制从主内存中读取最新的数据到写缓冲区/高速缓存中。内存屏障是一种CPU指令,在不同的CPU体系结构下实现方式可能不同。
因此,实际上Java中的volatile关键字和内存屏障直接关系密切。Java虚拟机通过内存屏障实现volatile变量的读写操作,保证了数据的可见性和一致性。
volatile 写之前的操作,都禁止重排序到volatile之后
volatile 读之后的操作, 都禁止重排序到 volatile之前
volatile写之后volatile读,禁止重排序