Java面试中经常会被问到Volatile关键字,面对这个问题,很多人的回答可能如下:
Volatile关键字是为了保证线程安全,线程安全意味着一个方法或类实例可以被多个线程同时使用,而不会出现任何问题。它提供的功能主要有两点:

  • 可见性
  • 防止指令重排

紧接着面试关可能让我们解释可见性

可见性:

如果两个线程在不同的处理器上运行,则每个线程可能具有自己的共享变量的本地副本。如果一个线程修改其值,则更改可能不会立即反映在主内存中的原始线程中。如果使用Volatile,则可以保证变量的值始终从主内存中读取,而不是从Thread的本地缓存中读取。这样就保证了不同线程之间的变量是彼此可见的。

在阐述可见性的时候可以想想下图。下图中处理器1,2就保存着变量的副本,导致变量的值不统一。

下面用代码进行验证:

public class VolatileTest {

    private static  int MY_INT = 0;

    public static void main(String[] args) {
        new ChangeListener().start();
        new ChangeMaker().start();
    }

    static class ChangeListener extends Thread {
        @Override
        public void run() {
            int local_value = MY_INT;
            while ( local_value < 5){
                if( local_value!= MY_INT){
                    System.out.println("改变 MY_INT " + MY_INT);
                    local_value= MY_INT;
                }
            }
        }
    }

    static class ChangeMaker extends Thread{
        @Override
        public void run() {

            int local_value = MY_INT;
            while (MY_INT <5){
                System.out.println("增加 MY_INT 至" + (local_value+1));
                MY_INT = ++local_value;
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) { e.printStackTrace(); }
            }
        }
    }
}

上面的代码中除了主线程之外还有两个线程,一个是ChangeListener,监听对于MY_INT的改变,另一个是ChangeMaker,对MY_INT执行累加操作,这两个操作都在本地有MY_INT的副本,如果不使用Volatile关键字修饰,ChangeMaker对MY_INT的改变ChangeListener就监听不到,执行的结果就如下面所示:

增加 MY_INT 至1
增加 MY_INT 至2
增加 MY_INT 至3
增加 MY_INT 至4
增加 MY_INT 至5

如果Volatile修饰了MY_INT变量,执行的结果如下所示:

增加 MY_INT 至1
改变 MY_INT 1
增加 MY_INT 至2
改变 MY_INT 2
增加 MY_INT 至3
改变 MY_INT 3
增加 MY_INT 至4
改变 MY_INT 4
增加 MY_INT 至5
改变 MY_INT 5

可以看到每一次的改变,ChangeListener都可以监听到。
讲完了可见性,如果面试官对并发这块感兴趣的话,可能会延伸到synchronized,讲讲synchronized和volatile的区别
详细的回答可以参考下面:

Java中的volatile关键字是字段修饰符,而sync则修改代码块和方法。

其实最根本的一点就是,synchronized可以实现原子性,也就说一个时刻只允许一个线程进入临界区。Volatile不能实现原子性。

public  class VolatileTest {

    private  volatile static int MY_INT = 0;

    public static void main(String[] args) {
        ExecutorService es = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 100; i++) {
            es.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println(++VolatileTest.MY_INT);
                    try {
                        Thread.sleep(40);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                };
            });
        }
        es.shutdown();
    }
}

上面的代码结果有可能并不能累加到100,这就是Volatile不能保证原子性的结果。
回答了上面的问题,你以为就完了吗?面试官可能会紧接着再问一个问题:long和double的读写时原子的吗?如果使用Volatile修饰之后呢?
首先,long和double的读写不是原子的,但是volatile修饰之后一定是原子的。
喜欢文章的朋友可以关注公众号“面经详解”,更多精彩等着你哦!
图片说明