volatile面试题总结

导读:本篇文章讲解 volatile面试题总结,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

1. 说说你对volatile关键字的理解

volatile是java虚拟机提供的轻量级的同步机制

  1. 保证可见性
  2. 不保证原子性
  3. 保证有序性

2. 能不能详细说下什么是内存可见性,什么又是重排序呢?

2.1 内存可见性

  1. 这个需要从Java内存模型说起
  2. 在目前的计算机中,CPU的计算速度远远大于计算机存储数据的速度,但是内存访问的速度就慢了很多。为了提升整体性能,在CPU和内存之间加入了高速缓存
  3. CPU将计算需要用到的数据暂存进缓存中。当计算结束后再将缓存中的数据存入到内存中。这样CPU的运算可以在缓存中高速进行。
  4. 说回JMM,也就是Java内存模型,JMM规定所有变量都是存在主存中的,类似于上面提到的普通内存,每个线程又包含自己的工作内存,所以线程的操作都是以工作内存为主,它们只能访问自己的工作内存,且工作前后都要把值在同步回主内存
  5. 使用了volatile后,操作数据的线程先从主内存中把数据read总线bus,然后load到自己的工作内存中。如果有线程对volatile修饰的变量进行操作并且写回了主内存,则其他已读取该变量的线程中,该变量副本将会失效。其他线程需要从主内存中重新加载一份最新的变量值。
  6. Volatile保证了共享变量的可见性。当有的线程修改了Volatile修饰的变量值并写回到主内存后,其他线程能立即看到最新的值。
    在这里插入图片描述

2.2 指令重排

  1. 系统为了提升执行效率,在不影响最终结果的前提下,系统会对要执行的指令进行重排序。
    重排序分为以下几种:
  • 编译器优化的重排序:编译器在不改变单线程程序语义的前提下重新安排语句的执行顺序。
  • 指令级并行的重排序:如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
  • 内存系统的重排序:由于数据读写过程中涉及到多个缓冲区,这使得加载和存储的操作看上去可能是乱序执行,于是需要内存系统的重排序。

JMM主要就是围绕着如何在并发过程中如何处理原子性、可见性和有序性这3个特征来建立的,通过解决这三个问题,可以解除缓存不一致的问题。

3. 那你具体说说这三个特性

  1. 原子性:Java中,对基本数据类型的读取和赋值操作是原子性操作,所谓原子性操作就是指这些操作是不可中断的,要做一定做完,要么就没有执行;赋值是原子操作,还只能是用数字赋值,用变量的话还多了一步读取变量值的操作
  2. 可见性:Java就是利用volatile来提供可见性的。当有的线程修改了Volatile修饰的变量值并写回到主内存后,其他线程能立即看到最新的值。 其实通过synchronized和Lock也能够保证可见性,线程在释放锁之前,会把共享变量值都刷回主存,但是synchronized和Lock的开销都更大
  3. 有序性:为了提升执行效率,JMM是允许编译器和处理器对指令重排序的,但是规定了as-if-serial语义,即不管怎么重排序,程序的执行结果不能改变。

4. 可见性的解决方案?

  1. 给代码加锁:使用synchronized的代码前后,线程会获得锁清空工作内存read将数据读到工作内存并load成为最新的副本,再通过store和write将数据写会主内存。而获取不到锁的线程会阻塞等待,所以变量的值一直都是最新的
  2. volatile修饰共享变量:使用了volatile后,操作数据的线程先从主内存中把数据read到工作内存,load成副本。如果有线程对volatile修饰的变量进行操作并且写回了主内存,则其他已读取该变量的线程中,该变量副本将会失效。其他线程需要从主内存中重新加载一份最新的变量值

5. volatile能保证可见性吗?

  1. Volatile能保证可见性
  2. 使用了volatile后,操作数据的线程先从主内存中把数据read到工作内存,load成副本中。
  3. 如果有线程对volatile修饰的变量进行操作并且写回了主内存,则其他已读取该变量的线程中,该变量副本将会失效。其他线程需要从主内存中重新加载一份最新的变量值

6. volatile能保证原子性吗?如何保证原子性?

在这里插入图片描述

  1. volatile不能保证原子性
  2. 这两步操作在左边的线程执行完第一步,但还没执行第二步时右边的线程抢过CPU控制权开始完成+1的操作后写入到主内存,于是左边的线程工作内存中的count副本失效了,相当于左边这一次+1的操作就被覆盖掉了。因此,Volatile不能保证原子性。
  3. 保证原子性,需要加锁
    在这里插入图片描述

7. 什么是指令重排?

系统为了提升执行效率,在不影响最终结果的前提下,系统会对要执行的指令进行重排序
在这里插入图片描述
重排序分为以下几种:

  1. 编译器优化的重排序:编译器在不改变单线程程序语义的前提下重新安排语句的执行顺序。
  2. 指令级并行的重排序:如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
  3. 内存系统的重排序:由于数据读写过程中涉及到多个缓冲区,这使得加载和存储的操作看上去可能是乱序执行,于是需要内存系统的重排序。

8. as-if-serial语义

不管怎么重排序,单线程程序的执行结果不能被改变。编译器、runtime和处理器都必须遵守“as-if-serial语义”。编译器和处理器不会对存在数据依赖关系的操作做重排序,因为这种重排序会改变执行结果

9. volatile能如何保证有序性?

  1. Volatile通过设置内存屏障(Memory Barrier),可以禁止指令重排,避免多线程环境下程序出现乱序执行的现象
  2. 通过插入内存屏障禁止在内存屏障前后的指令执行重排序优化。
  3. Memory Barrier的另外一个作用是强制刷出各种CPU的缓存数据,因此任何CPU上的线程都能读取到这些数据的最新版本。
  4. 总之,volatile变量正是通过内存屏障实现其在内存中的语义,即可见性和禁止重排优化。

在这里插入图片描述

10. MESI缓存一致性协议?

  1. volatile具有可见性,线程是怎么知道数据被其他线程更新的呢?这就跟MESI缓存一致性协议有关系。
  2. M(Modified修改):当cpu2对变量进行修改时,现在cpu内的缓存行中上锁,并向总线发信号,此时cpu2中的变量状态为M
  3. E(Exclusive独享):当cpu1读取一个变量时,该变量在工作内存中的状态是E
  4. S(Shared共享):当cpu2读取该变量时,两个cpu中该变量的状态由E转为S。
  5. I(Invalid无效):cpu1嗅探到变量被其他cpu修改的信号,于是将自己缓存行中的变量状态设置为i,即失效。则cpu1再从内存中获取最新数据。
    在这里插入图片描述

11. 总线风暴是什么?

  1. 由于Volatile的MESI缓存⼀致性协议,需要不断的从主内存嗅探和cas不断循环,⽆效交互会导致总线带宽达到峰值
  2. 所以不要⼤量使⽤Volatile,⾄于什么时候去使⽤Volatile,什么时候使⽤锁,根据场景区分。

12.什么时候去使⽤Volatile?

  1. 某个属性被多个线程共享,其中有⼀个线程修改了此属性,其他线程可以⽴即得到修改后的值,⽐如作为触发器,状态量标记,实现轻量级同步
  2. volatile可以在单例双重检查中实现可⻅性和禁⽌指令重排序,可以解决单例双重检查对象初始化代码执⾏乱序问题,从⽽保证安全性。

13. Volatile和Synchronized区别?

  1. volatile只能修饰实例变量和类变量,只能作⽤于属性,⽽synchronized可以修饰⽅法和代码块
  2. volatile保证数据的可⻅性,⽤于禁⽌指令重排序,但是不保证原⼦性;⽽synchronized是⼀种互斥的机制。
  3. volatile属性的读写操作都是⽆锁的,不需要花费时间在获取锁和释放锁上,所以说它是低成本的
  4. volatile可以看做是轻量版的synchronized,volatile不保证原⼦性,但是如果是对⼀个共享变量进⾏多个线程的赋值,⽽没有其他的操作,那么就可以⽤volatile来代替synchronized,因为赋值本身是有原⼦性的,⽽volatile⼜保证了可⻅性,所以就可以保证线程安全了。

14. volatile能替代synchronized吗?

  1. volatile属性的读写操作都是⽆锁的
  2. 它没有提供原⼦性和互斥性,它不能替代synchronized

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/65624.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!