Java关键字native和volatile

native

1.java 中的 native 修饰符表示被修饰的方法由本地语言实现。

2.有时java应用需要与java外面的环境交互。这是本地方法存在的主要原因,你可以想想java需要与一些底层系统如操作系统或某些硬件交换信息时的情况。

3.JVM支持着java语言本身和运行时库,它是java程序赖以生存的平台,通过使用本地方法,我们得以用java实现了jre的与底层系统的交互,甚至JVM的一些部分就是用C写的,还有,如果我们要使用一些java语言本身没有提供封装的操作系统的特性时,我们也需要使用本地方法

比如:Thread.start()方法,CAS里面调用的操作系统的CAS

volatile

关键字volatile在Java中本质上是一种修饰符,它用来修饰变量。

volatile关键字在多线程编程中非常重要,它保证了多个线程之间变量的可见性和有序性。

可见性

被volatile修饰的变量对于所有线程都是可见的,即当一个线程修改volatile变量的值后,其他线程立即可见修改后的值。写完后立即刷新回主内存并及时发出通知,大家可以去主内存拿最新版,前面的修改对后面所有线程可见

有序性(禁止指令重排序)

JVM会对代码进行优化,其中包括指令重排序,编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段,有时候会改变程序语句的先后顺序,如果一个操作的结果并不影响程序的正确性,那么JVM很有可能会将此操作与其后面的操作交换执行顺序。而在多线程环境中,这种指令重排序可能会导致程序输出不正确的结果,因此使用volatile关键字可以禁止指令重排序优化。

不保证原子性

并不保证操作具有原子性

如何实现volatile的可见性和有序性

当写一个volatile变量时,JVM会把该线程对应的本地内存中的共享变量值立即刷新回主内存中。.
当读一个volatile变量时,JVM会把该线程对应的本地内存设置为无效,重新回到主内存中读取最新共享变量

所以volatile的写内存语义是直接刷新到主内存中,读的内存语义是直接从主内存中读取

引入内存屏障的定义

内存屏障

内存屏障是一种机制,用于保障程序在多线程环境中的数据一致性。它可以保证指令的执行顺序,避免出现线程安全问题

内存屏障(也称内存栅栏,屏障指令等,是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作),避免代码重排序。内存屏障其实就是一种JVM指令,Java内存模型的重排规则会要求Java编译器在生成JVM指令时插入特定的内存屏障指令,通过这些内存屏障指令,volatile实现了Java内存模型中的可见性和有序性(禁重排)

可见性实现:

内存屏障之前的所有写操作都要回写到主内存。
内存屏障之后的所有读操作都能获得内存屏障之前的所有写操作的最新结果

实现了可见性

内存屏障分类:

Load Barrier(读屏障) 确保前面的读操作必须在它之前的写操作完成之后才执行
Store Barrier(写屏障) 确保后面的写操作必须在它之前的读操作完成之后才执行。
Full Fence(全屏障) 保证所有前面的操作都完成后再执行后面的操作。
StoreLoad Barrier(写-读屏障) 保证后面的读操作必须在前面的写操作完成后执行

内存屏障指令:

屏障类型 屏障指令 指令效果
LoadLoad屏障 读1;LoadLoad屏障;读2 保证读1在读2以及后续读取操作之前执行
StoreStore屏障 写1;StoreStore屏障;写2 写2及其后续写操作执行前,保证写1的操作已经刷新到主内存
LoadStore屏障 读1;LoadStore屏障;写2 写2及其后续的写操作执行前,保证读1的操作已经结束
StoreLoad屏障 写1;StoreLoad屏障;读2 保证写1的操作已经刷新到主内存之后,才会执行后续的读2操作

内存屏障刷内存数据:

读屏障(LoadBarrier):在读指令之前插入读屏障,让工作内存或CPU高速缓存当中的缓存数据失效,重新回到主内存中获取最新数据
写屏障(StoreBarrier):在写指令之后插入写屏障,强制把写缓冲区的数据刷回到主内存中

volatile读操作

在每个volatile读操作的后面插入一个LoadLoad屏障
在每个volatile读操作的后面插入一个LoadStore屏障

1

volatile写操作

在每个volatile写操作的前面插入一个StoreStore屏障
在每个volatile写操作的后面插入一个StoreLoad屏障

1

总结

Java中的volatile关键字会告诉Java虚拟机,在访问该变量时总是从主内存中读取数据,在修改该变量时总是将数据写回主内存中。这个特性可以保证在多线程环境下,不同线程访问该变量时不会出现数据不一致的问题。

为了实现这个特性,Java虚拟机会在volatile变量的读写操作前后加上内存屏障(Memory Barriers),它会强制将写缓冲区/高速缓存中的数据刷新到主内存中,或强制从主内存中读取最新的数据到写缓冲区/高速缓存中。内存屏障是一种CPU指令,在不同的CPU体系结构下实现方式可能不同。