Volatile 字段为实例字段的同步访问提供了一种免锁机制。如果声明一个字段为 volatile ,那么编译器和虚拟机就知道该字段可能被另一个线程并发更新。
编译器会插入适当的代码,以确保 如果一个线程对 volatile变量做了修改,这个修改对读取这个变量的所有其它线程可见。
public class Class2 {
private volatile int x;
public void count(){
x++;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
}
public static void main(String[] args) {
Class2 class2 = new Class2();
Thread thread1 = ThreadFactory.createThreadByRunnable(() -> {
try {
Thread.sleep(10); //让另一个线程启动
} catch (InterruptedException e) {
e.printStackTrace();
}
while (true){
int x = class2.getX();
class2.count(); //使x的值加一
if (class2.getX() < x){ //如果其它线程修改了x变量,则输出修改前后的值
System.out.printf("%s counted %d || %dtn",Thread.currentThread(),class2.getX(),x);
}
}
});
Thread thread2 = ThreadFactory.createThreadByRunnable(() -> {
while (true) {
int x = class2.getX();
System.out.print(Thread.currentThread() + ":");
class2.setX(x); //修改x的值
System.out.printf("SET x%dtn", x);
}
});
thread1.start();
thread2.start();
}
运行结果部分截图
- counted 后的两个数字分别表示 现在x的值 以及 该线程最初获得的x的值
第二行的输出结果分析:
- 第19行代码执行后,线程由 thread2 切换至 thread1,然后在 thread1 停留一段时间后再次切换至 thread2
- 此时 thread2 中的 x 已经和当前的 x 有了偏差
- 第21行代码执行后( 修改了x 变量),线程由 thread2 切换至 thread1,代码在 thread1 中运行至12行
- 发现 x 变量发生了修改,随后输出修改前后的值,线程此时再次切换至 thread2
- 执行第22行代码,并输出修改的值,可以发现 其修改的值和 thread1 获取的完全一样
结论
Volatile 字段 可以保证变量在并发时的内存可见性,但并不提供原子性
一个线程如果多次读取Volatile 字段,其前后的值可能发生改变
如果去掉 volatile 声明,则运行结果如下
thread1 将无法检查到变量x 的修改
补充
- 每个线程都有一个工作缓存副本
public class MyThread implements Runnable{
boolean flag = true;
//boolean volatile flag = true;
@Override
public void run() {
System.out.println("Enter in " + Thread.currentThread());
int i = 0;
while (flag){
i++;
}
System.out.println(Thread.currentThread() + "is exiting");
}
public static void main(String[] args) throws InterruptedException {
MyThread thread = new MyThread();
new Thread(thread).start();
Thread.sleep(1000);
thread.flag = false;
System.out.println("main is exiting");
}
}
- 若 该线程比main线程先启动,那么将陷入死循环(无 volatile 修饰)
- 其 flag 的值仍是其工作缓存内的值
- Volatile 字段解决了工作副本的可见性问题



