在Java中,volatile 是一个关键字,用于声明变量,具有特殊的内存语义。volatile 关键字主要用于确保变量的可见性和禁止指令重排序。它通常用于多线程编程环境中,以解决线程之间共享变量的同步问题。想要深入的了解volatile关键字的概念,还需要对[[Java内存模型JMM | 对java的内存模型JMM有一定的了解 ]]
volatile的内存语义和修饰变量的两个特点
当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值立即刷新回主内存中,当读一个volatile变量时,JMM会把该线程对应的本地内存设置为无效,重新回到主内存中读取最新共享变量。
可见性: 当一个变量被声明为 volatile 时,它的值在一个线程中的更改会立即对其他线程可见。这意味着如果一个线程修改了 volatile 变量的值,其他线程可以立即看到这个变化,而不会使用本地缓存的值
禁止指令重排序: volatile 变量的读取和写入操作会被插入内存屏障指令,这样可以确保指令不会被重排序,从而保持操作的有序性。
volatile修饰变量的可见性
无volatile关键字修饰案例
public class VolatileDemo {
static boolean flag = true;
/**
* 下边的方法验证了volatile的可见性
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"\t ======come in");
while (flag) {
} System.out.println(Thread.currentThread().getName()+"\t ======flag被设置成false,线程终止");
}, "t1").start();
TimeUnit.SECONDS.sleep(2);
flag = false;
System.out.println(Thread.currentThread().getName()+"\t main线程修改完成");
}
}

有关键字volatile修饰的变量
public class VolatileDemo {
static volatile boolean flag = true;
/**
* 下边的方法验证了volatile的可见性
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"\t ======come in");
while (flag) {
} System.out.println(Thread.currentThread().getName()+"\t ======flag被设置成false,线程终止");
}, "t1").start();
TimeUnit.SECONDS.sleep(2);
flag = false;
System.out.println(Thread.currentThread().getName()+"\t main线程修改完成");
}
}

从上述的两个例子中很容易的发现没有关键字volatile修饰的变量在状态改变后程序没有立即结束,而使用了volatile修饰的例子在状态改变后立即结束了。因为每个线程在运行过程中都有自己的工作内存,那么线程1在运行的时候,会将flag变量的值拷贝一份放在自己的工作内存当中,那么当线程2更改了flag变量的值之后,但是还没来得及写入主存当中,线程2转去做其他事情了,那么线程1由于不知道线程2对flag变量的更改,因此还会一直循环下去。
但是用volatile修饰之后会强制将修改的值立即写入主存,当线程2进行修改时,会导致线程1的工作内存中缓存变量flag的缓存行无效(反映到硬件层的话,就是CPU的L1或者L2缓存中对应的缓存行无效)。由于线程1的工作内存中缓存变量flag的缓存行无效,所以线程1再次读取变量flag的值时会去主存读取。那么线程1读取到的就是最新的正确的值。
volatile禁止重排
重排序: 是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段,有时候会改变程序的先后顺序,不存在数据依赖关系可以重排序,存在依赖关系的禁止重排序。
编译器优化重排序: 编译器在不改变但线程串行化语义的前提下,可以重新调整指令的执行顺序
指令级并行的重排序: 处理器使用指令级并行技术来讲多条指令重叠执行,若不存在数据以来行,处理器可以改变语句对应机器指令的执行顺序
内存系统的重排序: 由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是乱序执行
数据依赖性: 若两个操作访问统一变量,且这两个操作中有一个为写操作,此时两操作间就存在数据依赖性
//x、y为非volatile变量
//flag为volatile变量
x=2; //语句1
y=0; //语句2
flag=true; //语句3
x=4; //语句4
y=-1; //语句5
由于flag变量为volatile变量,那么在进行指令重排序的过程的时候,不会将语句3放到语句1、语句2前面,也不会讲语句3放到语句4、语句5后面。但是要注意语句1和语句2的顺序、语句4和语句5的顺序是不作任何保证的。并且volatile关键字能保证,执行到语句3时,语句1和语句2必定是执行完毕了的,且语句1和语句2的执行结果对语句3、语句4、语句5是可见的。
volatile日常使用场景
synchronized关键字是防止多个线程同时执行一段代码,那么就会很影响程序执行效率,而volatile关键字在某些情况下性能要优于synchronized,但是要注意volatile关键字是无法替代synchronized关键字的,因为volatile关键字无法保证操作的原子性。通常来说,使用volatile必须具备以下2个条件:
- 对变量的写操作不依赖于当前值
- 该变量没有包含在具有其他变量的不变式中
评论区