volatile关键字
作用
volatile
是 Java 虚拟机提供的轻量级的同步机制,主要用来确保变量被线程安全地读取和写入。
当一个变量定义为 volatile
后,它具备以下特性:
-
可见性:确保不同线程对这个变量操作的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
-
有序性:禁止进行指令重排序优化。
实现原理
在 Java 内存模型中,每个线程在运行时都有自己的工作内存,其中存放着主内存中变量副本的拷贝。
当线程对变量进行修改时,首先修改的是自己工作内存中的副本,然后线程再将修改后的副本值刷新到主内存中。
当其他线程需要读取该变量时,会从主内存中重新读取最新的副本到自己的工作内存中。
volatile
变量的特殊规则如下:
-
每次使用前都需要从主内存中刷新最新的值,保证读取的总是最新的数据。
-
每次修改后都需要立即同步回主内存中,保证其他线程可以看到最新的值。
使用场景
-
状态标记量:如中断标记、状态标记等。
-
单例模式的双重检查锁定:防止指令重排序。
注意事项
-
volatile
变量不适合作为状态的复合操作,因为volatile
不能保证复合操作的原子性。 -
volatile
只能保证变量的可见性和有序性,不能保证原子性。
volatile示例讲解
1. 可见性
Volatile保证了变量的可见性,即当一个线程修改了某个变量的值,这个新值对其他线程来说是立即可见的。
反面代码示例:不使用volatile时,可能导致线程读取到过时数据。
package com.hmblogs.backend.study.thread;public class VolatileExample {// 假设threadA和threadB同时访问isFlag变量,且没有使用volatile关键字static boolean isFlag = false;public static void main(String[] args) throws InterruptedException {Thread thread1 = new Thread(() -> {System.out.println("Thread 1 is running");// thread1中执行isFlag = true;});Thread thread2 = new Thread(() -> {System.out.println("Thread 2 is running");// thread2中执行if (!isFlag) {// 这里的代码可能会执行,即使isFlag已经被thread1设置为trueSystem.out.println("isFlag 是 false.");}});thread1.start();thread2.start();System.out.println("Both threads have finished their execution");}
}
正面代码示例:使用volatile确保变量值的实时更新。
package com.hmblogs.backend.study.thread;public class VolatileExample {// 假设threadA和threadB同时访问isFlag变量,且没有使用volatile关键字volatile static boolean isFlag = false;public static void main(String[] args) throws InterruptedException {Thread thread1 = new Thread(() -> {System.out.println("Thread 1 is running");// thread1中执行isFlag = true;});Thread thread2 = new Thread(() -> {System.out.println("Thread 2 is running");// thread2中执行if (!isFlag) {// 这里的代码可能会执行,即使isFlag已经被thread1设置为trueSystem.out.println("isFlag 是 false.");}});thread1.start();thread2.start();System.out.println("Both threads have finished their execution");}
}
验证结果:volatile没有对应的特性。这是什么原因呢?
2. 原子性
Volatile对单个变量的读写具有原子性,但复合操作(如i++)不是原子的。
反面代码示例:volatile变量在复合操作中可能出现问题。
package com.hmblogs.backend.study.thread;public class VolatileExample {static volatile int counter = 0;public static void main(String[] args) throws InterruptedException {Thread thread1 = new Thread(() -> {System.out.println("Thread 1 is running");// thread1中执行counter++;});Thread thread2 = new Thread(() -> {System.out.println("Thread 2 is running");// thread2中执行System.out.println("counter 是 "+counter+".");});thread1.start();thread2.start();System.out.println("Both threads have finished their execution");}
}
有时候打印的是1,有时候是0
正面代码示例:使用synchronized或原子类来保证复合操作的原子性。
package com.hmblogs.backend.study.thread;import java.util.concurrent.atomic.AtomicInteger;public class VolatileExample {static volatile AtomicInteger counter = new AtomicInteger();public static void main(String[] args) throws InterruptedException {Thread thread1 = new Thread(() -> {System.out.println("Thread 1 is running");// thread1中执行int temp = counter.getAndAdd(1);});Thread thread2 = new Thread(() -> {System.out.println("Thread 2 is running");// thread2中执行System.out.println("counter 是 "+counter.get()+".");});thread1.start();thread2.start();System.out.println("Both threads have finished their execution");}
}
一直都是0.
验证结果:volatile没有对应的特性。这是什么原因呢?
3. 有序性
Volatile变量还可以确保指令的有序性,防止JVM进行指令重排序。
反面代码示例:不使用volatile时,JVM可能进行指令重排序。
int a = 0;
boolean flag = false; // 假设这里不使用volatile
int b = 0;// 线程A执行
a = 1; // Step 1
flag = true; // Step 2
b = 2; // Step 3// 线程B执行
if (flag) {// JVM可能重排序,导致线程B先看到b=2,再看到a=1
}
正面代码示例:使用volatile防止指令重排序。
int a = 0;
volatile boolean flag = false;
int b = 0;// 线程A执行
a = 1; // Step 1
flag = true; // Step 2,volatile变量,确保前面操作不会重排序到后面
b = 2; // Step 3// 线程B执行
if (flag) {// 确保先看到a=1,再看到b=2
}
最后
Volatile是Java多线程编程中的一个重要关键字,它确保了变量的可见性、有序性和部分原子性。
正确使用volatile关键字可以避免多线程中的一些问题,如数据不一致和指令重排序等。
然而,需要注意的是,volatile并不能保证复合操作的原子性,对于这类操作,需要使用synchronized或原子类来保证线程安全。