目录
前言
原理
可见性
有序性
示例
示例1:解决可见性问题
示例2:解决有序性问题
总结
前言
在Java中,volatile 关键字用于解决并发场景下的可见性和有序性问题。通过理解 volatile 的工作原理和使用示例,可以更好地应用它来解决并发编程中的一些常见问题。
原理
可见性
在Java内存模型(JMM)中,每个线程都有自己的本地内存(缓存),线程对变量的读写操作先在本地内存中进行,然后再同步到主内存。这样,当一个线程修改了某个变量的值,其他线程可能无法立即看到这个变化,从而引发可见性问题。
volatile 关键字可以解决这个问题。当一个变量被声明为 volatile,它具有以下特性:
- 可见性:对
volatile变量的读写操作会直接操作主内存,而不是线程的本地内存。这意味着,当一个线程修改了volatile变量,其他线程立即可见。
有序性
在没有 volatile 的情况下,Java编译器、运行时和处理器可以对代码进行优化,重新排序指令。这可能会导致指令的执行顺序与代码的书写顺序不一致,从而引发有序性问题。
volatile 关键字通过内存屏障(Memory Barriers)确保了指令的有序性:
- 写屏障:在写入
volatile变量之前,会插入一个写屏障,确保在此之前的所有写操作都已写入主内存。 - 读屏障:在读取
volatile变量之后,会插入一个读屏障,确保在此之后的所有读操作都能从主内存读取最新的值。
示例
下面是一个示例,通过 volatile 关键字解决可见性和有序性问题:
示例1:解决可见性问题
public class VolatileVisibilityExample {private static volatile boolean running = true;public static void main(String[] args) {Thread worker = new Thread(() -> {while (running) {// Busy-wait loop}System.out.println("Worker thread stopped.");});worker.start();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}running = false;System.out.println("Main thread set running to false.");}
}
在这个示例中,running 变量被声明为 volatile,确保当主线程将 running 设置为 false 后,工作线程能够立即看到这个变化并终止循环。
示例2:解决有序性问题
public class VolatileOrderingExample {private static volatile int x = 0;private static volatile int y = 0;public static void main(String[] args) {Thread writer = new Thread(() -> {x = 1; // Ay = 2; // B});Thread reader = new Thread(() -> {int r1 = y; // Cint r2 = x; // DSystem.out.println("r1: " + r1 + ", r2: " + r2);});writer.start();try {writer.join(); // 确保writer线程先执行完} catch (InterruptedException e) {e.printStackTrace();}reader.start();}
}
在这个示例中,x 和 y 变量被声明为 volatile,确保写线程中的写操作(A和B)在读线程中的读操作(C和D)之前完成,从而保证有序性。在没有 volatile 的情况下,读线程可能会看到 y 已经被赋值为 2,而 x 仍然是初始值 0,导致输出不一致。
总结
- 可见性:
volatile确保变量的修改对所有线程立即可见。任何一个线程对volatile变量的修改都会立即写入主内存,并且其他线程对该变量的读取操作会直接从主内存读取最新的值。 - 有序性:
volatile通过内存屏障确保指令的有序执行。在对volatile变量的写操作前插入写屏障,确保写操作不会被重新排序;在对volatile变量的读操作后插入读屏障,确保读操作不会被重新排序。
通过使用 volatile 关键字,可以在多线程环境下确保变量的可见性和指令的有序性,从而避免竞态条件和内存可见性问题。然而,volatile 仅适用于某些特定场景,对于更复杂的同步问题,仍需使用其他同步机制,如 synchronized、Lock 等。