Java面试-基础篇之7

说一下Volatile关键字

volatile使Java中的关键字,它的中文意思是“不稳定的”。在Java中,该关键字主要修饰变量(实例变量和类变量<静态变量,使用static修饰>),而此变量一般是临界资源。
要说volatile的作用,就需要先了解一下计算机的内存模型和JMM(Java Memory Model)的内存模型。
计算机在执行程序时,每条指令是在CPU中完成的。执行完指令就将处理完成的数据放回内存中。随着CPU技术的发展,CPU的执行速度越来越快,而从内存中读写数据的速度远远不如CPU,这导致CPU在执行程序时,需要等待数据从CPU读出或者写入,这个等待的时间无法使CPU发挥它的真正作用。于是我们在CPU和内存之间添加了高速缓存在来接内存读写速度于CPU计算速度匹配的问题。CPU与内存之间可能有多级缓存,常见的有一级缓存,二级缓存,三级缓存。为了能够解释清楚内存模型的问题,在这里我们假设内存和CPU之间只有一级缓存。

而现在一个程序的执行过程就变成了高速缓存首先从内存中读取一部分数据,一共CPU计算使用,如果缓存中的数据被修改了,那么就会按照程序的设定实时或者延迟写入内存中。随着CPU的发展目前的单核CPU已经发展成了多核CPU,每个CPU对应一个高速缓存,那就存在了一种问题,如果多个缓存缓存了同一部分数据,如果有一个CPU将缓存中的数据修改了,还未写回内存,那其他CPU并不能看到已经被修改的数据,还是按照自己缓存中的数据进行计算,这里就存在了一个数据不一致的情况。
比如CPU中的一个线程,在执行过程中修改了一个共享变量(全局变量),但该变量只有等当前的线程执行完毕后,才会写回内存通知其他缓存,此时,CPU2也要对该变量操作,但是它读到的是已经过时的变量值,这就造成了缓存不一致的问题。
还有在硬件中处理器可能会对输入的代码进行优化乱序处理,这可能会导致原子性问题;在JVM的即时编译器JIT(Just in time)也会做指令重排。当然这些乱序核重排处理并不是随便变化的,是编译器或者处理器根据规则,在不影响单线程执行语义歧义的情况下完成的。但是在多线程中,由于CPU是轮转时间片给线程用,所以这就可能导致多线程间的有序性问题。
在Java语言中,有一种虚拟的内存模型,叫做JMM。该模型是一种处理规范,用来屏蔽各个硬件平台和操作系统的内存访问差异,以实现Java程序在各种平台下都能达到一致的内存访问效果。它定义了内存中变量的访问规则,或者说定义了程序执行的次序。但是在Java内存模型中,并没有限制执行引擎使用处理器寄存器或者高速缓存来提升执行速度,也没有限制编译器对指令进行重排序。也就是说,在JMM内存模型规范下,也会存在环迅一致性问题和指令重排序的问题。因为并不是所有的代码和变量都会出现原子性问题,可见性问题和有序性问题,有很多是在代码层面,也就是程序员的手中进行控制的。JMM是为了统一各平台的访问内存规则而生的,他也为我们提供了并发编程3大问题的解决方案。
在了解具体的解决方案之前,再来回顾一下3大并发问题。

  1. 原子性问题。原子性在操作层面的解释是,程序的一个或多个操作,要么全都执行,要么全都不执行,此处再狭义的将,简单的赋值操作,x=10,我们将其视为一个原子性操作,指的是在指令级别无法继续拆解,但假如增加了一个命令y=x,则不是原子性操作。因为y=x在指令层面可分为两步。一是取出x的值,二是将x的值赋值给y。在实际的执行中,程序中的原子性无法保证。因为在并发的环境下,假如某个线程进行中被其他线程抢占,则该程序操作的原子性也被破坏了。此时JMM提供给我们一些命令,让我们在代码层面就声明某段程序的同步性,然后在执行过程中,显式的标记末段程序需要特殊对待,让其在一个线程中执行部内其他程序抢占。
  2. 可见性问题。可见性问题实际是共享变量的问题,JMM中可用Volatile修饰变量。使用Volatile修饰的变量,在指令层面会对该共享变量监控起来,并提供一些变量变更后的措施。当该共享变量被修改后,修改后的值被立即更新到内存中,并使其他缓存中的值失效,致使其他线程用到共享变量时,知道数据已经过时,需要重新从内存中获取。另外,使用synchronized和Lock也能够保证可见性。因为他们能够保证同一时刻只有一个线程获取该共享变量的执行程序,因为他们使用某些方法,使其他访问的线程阻塞或者自旋。在持有该变量修改权限(锁)的线程修改完后,写回内存,然后释放锁,也就保证了该共享变量的可见性。
  3. 有序性问题。在JMM中,编译器和处理器会对指令进行重排序。多线程过程中,会因为重排序 而导致数据异常。比较著名的案例是单例模式中的双重检测机制。在JMM中提供了volatile来保证一定的有序性。当然,synchronized和Lock也可以保证。JMM还具备了先天的有序性,通常被称为happens-before原则(先天发生原则)。如果2个操作无法从happens-before原则推导出来,那么就不能保证他们的有序性。JVM可随意随他们重排序。
    happens-before原则:程序层序、锁定原则、volatile原则、传递原则
    线程启动、线程中断、线程中结、对象终结
    volatile
    一个变量被volatile修饰后,具备了两层含义:
    1.该共享变量的操作可见
    2.禁止重排序(汇编代码中会多一个lock的前缀指令,相当于一个内存屏障)
    volatile不能够保证原子性

这篇好多啊···

posted @   行业小白  阅读(17)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· winform 绘制太阳,地球,月球 运作规律
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示