Java内存模型(JMM)都是围绕着原子性、有序性和可见性展开的。关键字 volatile 就是 Java 用来声明可见性的关键字。当使用 volatile 关键字声明一个变量时,就等于告诉虚拟机,这个变量极有可能被某些程序或者线程修改。
当程序在运行过程中,会将运算需要的数据从主存复制一份到CPU的高速缓存当中,那么CPU进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据,当运算结束之后,再将高速缓存中的数据刷新到主存当中。这种方式在单线程中运行是没有任何问题的,但是在多线程中运行就会有问题了。在多核CPU中,每条线程可能运行于不同的CPU中,因此每个线程运行时有自己的高速缓存(对单核CPU来说,其实也会出现这种问题,只不过是以线程调度的形式来分别执行的)。
普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。
而当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。另外,通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。
关键字 volatile 能在一定程度上保证有序性:
- 当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;
- 在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。
package com.demo.base;
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
MyRunnable myRunnable = new MyRunnable();
// 创建一个线程对象
Thread thread = new Thread(myRunnable);
// 启动线程
thread.start();
Thread.sleep(1000);
myRunnable.flag = true;
System.out.println("主线程执行结束");
}
}
class MyRunnable implements Runnable {
// 没有 volatile 关键字修饰时,线程会一直启动着,不会自动结束。
// boolean flag = false;
volatile boolean flag = false;
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println("线程: " + name + " 开始执行。。。");
while (! flag){
}
System.out.println("线程: " + name + " 结束。");
}
}