美文网首页jvmjava
Java内存模型(3个特性)、volatile变量的特殊规则

Java内存模型(3个特性)、volatile变量的特殊规则

作者: Java后端技术 | 来源:发表于2017-04-09 16:15 被阅读391次

    一、Java内存模型

    Java内存模型在Java虚拟机规范中定义的,用来屏蔽各种硬件和操作系统的内存访问差异,以达到Java应用在各类平台下都有一致的内存访问效果。也就是说Java内存模型主要是定义程序中“共享变量”的访问规则,即虚拟机将“共享变量”存储到内存以及从内存中取出这样的底层细节。为什么是共享变量呢,因为线程私有的变量不会存在数据竞争的问题。

    二、主内存和工作内存

    Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该条线程所使用到的变量在主内存中的副本。线程的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。不同线程之间无法直接访问对方线程中工作内存的变量,线程间变量值的传递需要通过主内存来完成。他们之间的关系如下图所示:

    三、内存之间的交互操作

    主内存与工作内存之间的交互协议,Java内存模型定义了以下八种操作来完成,虚拟机实现时保证以下每一种操作都是原子性的不可再分的。

    1、lock(锁定):作用于主内存中的变量,它把一个变量标识为一条线程独占的状态。

    2、unlock(解锁):作用于主内存中的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。

    3、read(读取):作用于主内存中的变量,它把一个变量从主内存传输到线程的工作内存中。

    4、load(载入):作用于工作内存中的变量,它把read操作读取的值放入工作内存的变量副本中

    5、use(使用):作用于工作内存中的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时都会执行这个操作。

    6、assign(赋值):作用于工作内存中的变量,它把一个从执行引擎接收到的值赋给工作内存中的变量,每当虚拟机遇到一个给变量赋值的字节码指令时都会执行这个操作。

    7、store(存储):作用于工作内存中的变量,它把一个变量的值传递到主内存中。

    8、write(写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。

    例如:如果要把一个变量从主内存复制到工作内存,就要顺序的执行read、load操作。如果要把一个变量工作内存回写到主内存,就需要顺序的执行store、write操作。需要注意的是,Java内存模型只定义了这两组操作的顺序执行,但是没有保证连续执行,也就是说,期间可能插入其他操作:read A、read B、load B、load A。Java内存模型规定了,执行以上八种操作必须满足以下规则:

    1、不允许read和load、store和write操作之一单独出现,原因很好理解。

    2、不允许一个线程的丢弃assign操作。也就是说,变量在工作内存中有改变后必须同步到主内存。

    3、不允许一个线程无原因的(没有发生过任何赋值操作)把数据同步到主内存。

    4、一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load、assign)的变量。

    5、一个变量在同一时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次执行后,只有相同次数的unlock操作,变量才会被解锁。

    6、如果对一个变量执行lock操作,那么将会清空工作内存中此变量副本、在执行引擎使用这个变量之前需要重新进行load或assign操作来初始化变量的值

    7、如果一个变量没有被lock锁定,那么久不允许对它进行unlock操作,也不允许线程unlock其他线程锁定的变量。

    8、对一个变量进行unlock操作前,必须先把变量同步回主内存。

    四、volatile类型变量的特殊规则

    假设T表示一个线程、V1和V2分别表示两个volatile类型变量,那么在进行read、load、use、assign、store和write操作时需要满足以下规则:

    1、只有当线程T对变量V1执行的前一个动作是load的时候,线程T才能对变量V1执行use动作;并且,只有当线程T对变量V1执行的后一个动作是use的时候,线程T才能对变量V1执行load动作。线程T对变量V1的use动作可以认为是和线程T对变量V1的load、read动作相关联,必须连续一起出现。这条规则要求在工作内存中,每次使用V1前都必须先从主内存刷新最新的值,用于保证能看见其他线程对变量V1所做的修改后的值。即使用变量:read->load->use

    2、只有当线程T对变量V1执行的前一个动作是assign的时候,线程T才能对变量V1执行store动作;并且,只有当线程T对变量V1执行的后一个动作是store的时候,线程T才能对变量V1执行assign动作。线程T对变量V1的assign动作可以认为是和线程T对变量V1的store、write动作相关联,必须连续一起出现。这条规则要求在工作内存中,每次修改V1后都必须立刻同步回主内存中,用于保证其他线程可以看到自己对变量V1所做的修改。即修改变量:assign->store->write

    3、假定动作A是线程T对变量V1实施的use或assign动作,假定动作F是和动作A相关联的load或store动作,假定动作P是和动作F相应的对变量V1的read或write动作;类似的,假定动作B是线程T对变量V2实施的use或assign动作,假定动作G是和动作B相关联的load或store动作,假定动作Q是和动作G相应的对变量V2的read或write动作。如果A先于B,那么P先于Q。这条规则要求volatile修饰的变量不会被指令重排序优化,保证代码的执行顺序与程序的顺序相同。

    有兴趣可以调试一下下面的代码:

    五、原子性、可见性和有序性

    其实Java内存模型就是围绕着在并发过程中如何处理原子性、可见性和有序性这三个特性来建立的。

    1、原子性:由Java内存模型类直接保证原子性变量的操作包括read、load、use、store和write。可以认为基本数据类型的访问读写是具备原子性的。另外需要注意的是非原子性协定,即:Java内存模型中定了一条相对宽松的规定,允许虚拟机将没有被volatile修饰的64位数据读写操作分为两次32位操作进行,也就是说,对long、double这种64位的数据类型的read、load、store和write操作可以不保证原子性操作。这种情况在64的虚拟机下是不会发生的,64位虚拟机下是原子性的,但是在32位的虚拟机下,这组操作不是原子性的。如果要更大范围的原子性保证,Java内存模型提供了lock和unlock操作,字节码指令对应的是monitorenter和monitorexit,反映到Java代码就是synchronized了。

    2、可见性:可见性是指一个线程修改了共享变量的值,其他线程能立即得知这个修改。上边在讲volatile变量的时候讲过,Java内存模型是通过在变量修改后立即将最新的值同步到主内存、在变量读取前从主内存再次读取最新值来保证可见性的。volatile变量与普通变量的区别是,volatile变量的特殊规则保证了最新值能立刻同步到主内存。而普通变量不能保证这一点。除了volatile外,还有synchronized和final关键字能保证可见性。synchronized的可见性对应Java内存模型中lock和unlock,lock会清空工作内存中变量的副本,unlock操作前会将变量同步到主内存(前边有提到)。

    3、有序性:如果从本线程里观察,所有操作都是有序的。如果从一个线程观察另一个线程,所有的操作可能都是无序的。也就是说,线程内表现为串行语义,多个线程间指的是指令重排序和工作内存和主内存同步延迟的问题。

    更多好文请关注微信公众号

    相关文章

      网友评论

      • fbad7ce3e463:作者的内容应该是深入理解Java虚拟机里面的 虽然是引用 但我觉得已经把这部分的重点内容给指出来了 感谢
      • 165e99d697e1:你这个文章里面的内容比较好,但是举例都是很老的情况了,应该指明jdk版本,新的版本早就有优化了。有实验才有收获

      本文标题:Java内存模型(3个特性)、volatile变量的特殊规则

      本文链接:https://www.haomeiwen.com/subject/zpbdattx.html