Java内存模型(JMM)都是围绕着原子性、有序性和可见性展开的。关键字 volatile 就是 Java 用来声明可见性的关键字。当使用 volatile 关键字声明一个变量时,就等于告诉虚拟机,这个变量极有可能被某些程序或者线程修改。

    当程序在运行过程中,会将运算需要的数据从主存复制一份到CPU的高速缓存当中,那么CPU进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据,当运算结束之后,再将高速缓存中的数据刷新到主存当中。这种方式在单线程中运行是没有任何问题的,但是在多线程中运行就会有问题了。在多核CPU中,每条线程可能运行于不同的CPU中,因此每个线程运行时有自己的高速缓存(对单核CPU来说,其实也会出现这种问题,只不过是以线程调度的形式来分别执行的)。

    普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。

    而当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。另外,通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。

    关键字 volatile 能在一定程度上保证有序性:

    • 当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;
    • 在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。


    1. package com.demo.base;
    2. public class ThreadDemo {
    3. public static void main(String[] args) throws InterruptedException {
    4. MyRunnable myRunnable = new MyRunnable();
    5. // 创建一个线程对象
    6. Thread thread = new Thread(myRunnable);
    7. // 启动线程
    8. thread.start();
    9. Thread.sleep(1000);
    10. myRunnable.flag = true;
    11. System.out.println("主线程执行结束");
    12. }
    13. }
    14. class MyRunnable implements Runnable {
    15. // 没有 volatile 关键字修饰时,线程会一直启动着,不会自动结束。
    16. // boolean flag = false;
    17. volatile boolean flag = false;
    18. @Override
    19. public void run() {
    20. String name = Thread.currentThread().getName();
    21. System.out.println("线程: " + name + " 开始执行。。。");
    22. while (! flag){
    23. }
    24. System.out.println("线程: " + name + " 结束。");
    25. }
    26. }