Java里面有2 个关键字可以实现同步机制,分别是volatile和synchronized。二者的区别和用法不在本文讨论范围,网上有很多关于这方面的介绍。本文的重点是Java内存模型对于volatile关键字有哪些特殊的规则,学习这些特殊规则会对volatile关键字有更深的理解。
建议优先阅读 《深入理解JAVA虚拟机》学习笔记--JAVA内存模型 这篇文章,先了解8种操作指令,会更容易理解下面的内容。
volatile特性
1. 可见性
当一个变量定义为volatile之后,它将对所有线程可见,这里的“可见性”是指当一个线程修改了这个变量的值,新值对于其它线程来说是可以立即得知的。
- 注意:volatile变量在各个线程的工作内存中不存在不一致性的问题(在各个线程的工作内存中,volatile变量也可以存在不一致的情况,但由于每次使用变量之前都要先刷新,执行引擎看不到不一致的情况,因此可以认为不存在不一致性的问题),但是Java里面的运行并非原子操作,导致volatile变量的运算在并发下一样是不安全的。测试代码可参考文章《深入分析Volatile的实现原理》中的“5、volatile不能保证原子性”部分。
- 使用场景:
(1)运算结果并不依赖变量当前值,或者能够确保只有单一的线程修改变量的值。
(2)变量不需要与其它的状态变量共同参与不变约束。
2. 禁止指令重排序
volatile关键字能禁止指令重排序,所以volatile能在一定程度上保证有序性。volatile关键字禁止指令重排序有两层意思:
(1)当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;
(2)在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。
下面是本文的主要内容,在Java内存模型中对volatile变量定义的特殊规则。
首先,先看一张图,下图为Java内存模型中定义的8种操作的描述图,具体详细内容参考:《深入理解JAVA虚拟机》学习笔记--JAVA内存模型。
特殊规则
假定T表示一个线程,V和W分别表示两个volatile型变量,那么在进行read、load、use、assign、store和write操作时需要满足如下规则:
- 只有当线程T对变量V执行的前一个动作是load的时候,线程T才能对变量V执行use操作;并且,只有当线程T对变量V执行的后一个动作是use的时候,线程T才能对变量V执行load动作。线程T对变量V的use动作可以认为是线程T对变量V的load、read动作相关联,必须连续一起出现(这条规则要求在工作内存中,每次 使用V前都必须先从主内存刷新最新的值,用于保证能看见其它线程对变量V所做的修改后的值)。
- 只有当线程T对变量V执行的前一个动作是assign的时候,线程T才能对变量V执行store动作;并且,只有当线程T对变量V执行的后一个动作是store的时候,线程T才能对变量V执行assign动作。线程T对变量V的assign动作可以认为是线程T对变量V的store、write动作相关联,必须连续一起出现(这条规则要求在工作内存中,每次修改V后都必须立刻同步回主内存中,用于保证其他线程可以看到自己对变量V所做的修改)。
- 假定动作A是线程T对变量V实施的use或assign动作,假定动作F是和动作A相关联的load或store动作,假定动作P是和动作F相应的对变量V的read或write动作;类似的,假定动作B是线程T对变量W实施的use或assign动作,假定动作G是和动作B相关联的load或store动作,假定动作Q是和动作G相应的对变量W的read或write动作。如果A先于B,那么P先于Q(这条规则要求volatile修饰的变量不会被指令重排序优化,保证代码的执行顺序与程序的顺序相同)。
网友评论