volatile的作用
- 禁止编译器优化:编译器在编译代码时会进行优化,将一些看似无用的变量读写操作删除或重新排序,以提高代码执行效率。但在多线程环境下,这种优化可能会导致问题,因为一个线程修改了变量的值,但其他线程可能不会立即看到这个变化。使用
volatile告诉编译器不要对变量进行这种优化,确保变量的读写操作按照代码的顺序执行。 - 保证可见性:
volatile可以确保一个线程对变量的修改对其他线程可见。也就是说,如果一个线程修改了一个volatile变量的值,那么其他线程在读取这个变量的值时会看到最新的修改,而不会使用缓存或旧值。
volatile的原理
volatile的原理与内存模型、缓存一致性协议和内存屏障有关。
- 内存模型:每个线程都有自己的工作内存,它们从主内存中读取共享变量的值。当线程修改了一个共享变量的值,这个修改只会在自己的工作内存中,而不会立即反映到主内存中。其他线程在读取这个变量时,可能会读取到旧的值,因为它们的工作内存中还没有这个变量的最新值。
- 缓存一致性协议:当CPU写数据时,如果发现操作的变量是共享变量(即在其他CPU中也存在该变量的副本),那么它会发出信号通知其他CPU将该变量的缓存行设置为无效状态。当其他CPU使用这个变量时,首先会去嗅探是否有对该变量更改的信号,当发现这个变量的缓存行已经无效时,会重新从内存中读取这个变量。
- 内存屏障:是一类同步屏障指令,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作。内存屏障之前的所有写操作都要写入内存,内存屏障之后的读操作都可以获得同步屏障之前的写操作的结果。
volatile如何保证可见性
当JVM将.class文件编译为具体的CPU执行指令(即机器码)后,加了volatile修饰的共享变量,会在指令前面加上一个以lock为前缀的指令。这个lock前缀的指令会引发两件事情:
- 将当前处理器缓存行的数据写回到系统内存。
- 一个处理器的缓存回写到内存会导致其他处理器的缓存失效。
这两点确保了当一个线程修改了一个volatile变量的值,其他线程会立即看到这个修改,从而保证了可见性。
总之,volatile通过禁止编译器优化、利用缓存一致性协议和内存屏障来确保多线程环境下对变量的可见性。然而,它并不能保证对变量的操作是原子的,因此在使用volatile时要慎重。