您的位置:首页 > 编程语言 > Java开发

Java中volatile关键字深度解析

2017-09-03 11:51 204 查看
一、内存模型的相关概念
大家都知道,计算机执行程序时,每条指令都是在CPU中执行的,而执行指令过程中,势必涉及到数据的读取和写入。由于程序运行过程中的临时数据是存放在内存当中的,这就存在一个问题,由于CPU执行指令的速度很快,而从内存读取和写入数据的速度慢得多,因此如果任何时候对数据的操作都要通过和内存的交互来进行,会大大降低执行速度。因此在CPU里边就有了高速缓存。
也就是,当程序运行过程中,会将运算需要的数据从主存复制一份到CPU的高速缓存中,那么CPU进行计算的时候就可以直接从它的高速缓存中读取和写入数据,当运行结束之后,再将高速缓存中的数据刷新到主存当中。
如果一个变量在多个CPU中都存在缓存(一般在多线程编程时才会出现),那么就可能存在缓存不一致问题。
为了解决缓存不一致问题,通常来说有一下2种解决方法:
1)通过在总线上加LOCK#锁的方式
2)通过缓存一致性协议
这两种方式都是在硬件层面上提供的方式。
在早期的CPU当中,是通过在总线上加LOCK#锁的形式来解决缓存不一致的问题的。因为CPU和其他部件进行通信时通过总线来进行的,如果对总线加LOCK#锁的话,也就是说阻塞了其他CPU对其他部件访问(如内存),从而使得只能有一个CPU能使用这个变量的地址。只有等到一个CPU的代码执行完毕之后,其他CPU才能从共享变量所在内存读取该变量,然后进行相应的操作。
但是这种方式会有一个问题,由于在锁住总线期间,其他CPU无法访问内存,导致效率低下。
所以就出现了缓存一致性协议。最出名的就是Intel的MESI协议,该协议保证了每个缓存中使用的共享变量的副本是一致的。它核心的思想是:当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU缓存中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从主存中重新读取。



二、并发编程中的三个变量
1.
原子性
即一个操作或者多个操作要么全部执行并且执行过程中不会被任何因素打断,要么就都不执行。
2.
可见性
当多个线程访问一个共享变量时,其中一个线程修改了此变量的值,其他线程能够立即看到修改后的值。
3.
有序性
程序执行的顺序按照代码的先后顺序执行
一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它保证程序最终执行结果和代码顺序执行的结果是一致的。指令冲排序不会影响单个线程的执行,但是会影响到线程并发执行的正确性。
也就是说,要想并发程序正确执行,必须保证原子性、可见性和有序性。只要由一个没有被保证,就有可能会导致程序运行不正确。
三、Java内存模型
在Java虚拟机规范中试图定义一种Java内存模型来屏蔽各个硬件平台和操作系统的内存访问差异,以实现让Java程序在各种平台下能达到一致的内存访问效果。注意,为了获得较好的执行性能,Java内存模型并没有限制执行引擎使用寄存器或者高速缓存来提升指令执行速度,也没有限制编译器对指令进行重排序。也就说,在Java内存模型中,也会存在缓存不一致性问题和指令重排序问题。
Java内存模型规定所有的变量都时存在主存中,每个线程由自己的工作内存。线程对变量的所有操作都是在自己的工作内存中进行,而不能直接对主存进行操作。并且每个线程不能访问其他线程的工作内存。
1.
原子性
在Java中,对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行。
下面哪些时原子性操作:
1)x = 10;
2)y = x;
3)x++;
4)x = x + 1;

只有语句1是原子操作。语句2)包含两个操作,先读取x的值,再将x的值写入工作内存。3)和4)都是三个操作:读取x的值,进行加1操作,写入新的值。
2.
可见性
Java提供了volatile、synchronize和lock关键字来保证可见性。
3.
有序性
在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
在Java中可以通过关键字volatile来保证一定的有序性。另外可以通过synchronized和lock来保证有序性,很显然,synchronized和lock保证每个时刻是有一个线程执行同步代码,相当于让线程顺序执行同步代码,自然就保证了有序性。
另外,Java内存模型具备一些先天的有序性,即不需要通过任何手段就能保证的有序性,这个通常称为happen-before原则。如果两个操作的执行次序无法从happen-before原则推到出来,那么我们就不能保证他们的有序性,虚拟机可以随意对他们进行重排序。
四、volatile关键字的作用
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备两层含义:
1)保证不同线程对这个变量进行操作时的可见性
2)禁止指令进行重排序
volatile关键字能禁止指令重排序,所以volatile能在一定程度上保证有序性。
volatile关键字禁止指令重排序有两层含义:
a.当程序执行到volatile变量的读操作和写操作时,在其前面的操作的更改肯定全部已经进行,且结果对后面的操作可见;在其后面的操作肯定还没有执行;
b.在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。
五、volatile的原理和实现机制
“观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”
lock前缀指令实际上相当于一个内存屏障,它会提供3个功能:
1)确保指令冲排序时,不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,它前面的操作已经全部完成;
2)它会强制对缓存的修改操作立即写入主存;
3)如果时写操作,它会导致其他CPU中对应的缓存行失效;
六、使用volatile关键字的场景
通常来说,使用volatile必须具备一下2个条件:
1)对变量的写操作不依赖于变量的当前值
2)该变量没有包含在具有其他变量的不变式中
这些条件表明,可以被写入volatile变量的这些有效值独立于任何程序的状态,包括变量的当前状态。

参考内容: http://www.cnblogs.com/dolphin0520/p/3920373.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息