volatile关键字相关面试题

1、说说volatile关键字的特性

被volatile修饰的共享变量,就具有了以下两点特性:

  • 保证了不同线程对该变量操作的内存可见性;
  • 禁止指令重排序;

2、JMM有哪些特性?

可见性,还有原子性和有序性。

3、volatile能保证原子性吗?

volatile不能保证原子性,它只是对单个volatile变量的读/写具有原子性,但是对于类似i 这样的复合操作就无法保证了。

如下代码,从直观上来讲,感觉输出结果为10000,但实际上并不能保证,就是因为inc 操作属于复合操作。

public class Test {

    public volatile int inc = 0;

 

    public void increase() {

        inc  ;

    }

 

    public static void main(String[] args) {

        final Test test = new Test();

        for(int i=0;i<10;i  ){

            new Thread(){

                public void run() {

                    for(int j=0;j<1000;j  )

                        test.increase();

                };

            }.start();

        }

 

        while(Thread.activeCount()>1)  //保证前面的线程都执行完

            Thread.yield();

        System.out.println(test.inc);

    }

假设线程A,读取了inc的值为10,然被阻塞,因未对变量进行修改,未触发volatile规则。线程B此时也读取inc的值,主存里inc的值依旧为10,做自增,然后立刻写回主存,值为11。此时线程A执行,由于工作内存里保存的是10,所以继续做自增,再写回主存,11又被写了一遍。所以虽然两个线程执行了两次increase(),结果却只加了一次。

有人说,volatile不是会使缓存行无效的吗?但是这里线程A读取之后并没有修改inc值,线程B读取时依旧是10。又有人说,线程B将11写回主存,不会把线程A的缓存行设为无效吗?只有在做读取操作时,发现自己缓存行无效,才会去读主存的值,而线程A的读取操作在线程B写入之前已经做过了,所以这里线程A只能继续做自增了。

针对这种情况,只能使用synchronized、Lock或并发包下的atomic的原子操作类。

4、synchronized与volatile之间的区别

volatile本质是在告诉JVM当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。

volatile仅能使用在变量级别;synchronized则可以使用在变量、方法和类级别的;

volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性;

volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。

volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。

5、volatile底层的实现机制是怎样的?

如果把加入volatile关键字的代码和未加入volatile关键字的代码都生成汇编代码,会发现加入volatile关键字的代码会多出一个lock前缀指令。

1 、重排序时不能把后面的指令重排序到内存屏障之前的位置

2、使得本CPU的Cache写入内存

3、写入动作也会引起别的CPU或者别的内核无效化其Cache,相当于让新写入的值对别的线程可见。

6、volatile的使用场景

1、修饰状态变量

2、单例模式的实现,双重检查锁定

7、何时在Java中使用volatile变量

在volatile关键字的学习中最重要的一点是了解何时在Java中使用volatile变量。许多程序员知道什么是volatile变量,它是如何工作的,但他们从来没有真正使用volatile来实现任何实际目的。这里有几个示例演示何时在Java中使用Volatile关键字:
1)如果你想以原子方式读写long类型变量和double类型变量,可以使用Volatile变量。 long和double都是64位数据类型,默认情况下,long和double的写入不是原子和平台依赖。许多平台在长和双变量2步执行写操作,在每一步写32位,由于这可能使一个线程看到从两个不同的写32位。您可以通过在Java中使用long和double变量volatile来避免此问题。
2)在某些情况下,volatile可以用作实现Java同步的一种替代方法,如Visibility。用volatile变量,保证所有读写器线程在写操作完成后都会看到volatile变量的更新值,而不用volatile关键字,不同的读线程可能会看到不同的值。
3)volatile变量可用于通知编译器一个特定的字段被多个线程访问,这将阻止编译器进行任何重新排序或在多线程环境中不希望的任何类型的优化。如果没有volatile变量,编译器可以重新排序代码,可以自由地缓存volatile变量的值,而不是总是从主内存中读取数据。就像下面没有volatile变量的例子可能会导致无限循环

private boolean isActive = thread;

public void printMessage(){

while(isActive) {

System.out.println("Thread is Active");

}

}

如果没有volatile修饰符,则不能保证一个线程从其他线程看到isActive的更新值。编译器还可以自由缓存isActive的值,而不是在每次迭代中从主存储器中读取。通过使isActive成为一个volatile变量,可以避免这些问题。

4)另一个可以使用volatile变量的地方是在Singleton模式下修复双重检查的锁定。

 

发布了45 篇原创文章 · 获赞 0 · 访问量 1910

猜你喜欢

转载自blog.csdn.net/weixin_41804367/article/details/102995580