java并发编程的艺术【三】-【一】java内存模型基础

 2019-12-10 16:01  阅读(830)
文章分类:Java Core

并发编程的两个关键问题
1,并发编程中需要处理两个关键的问题,第一个是线程之间的通讯, 通讯是指的是线程之间如何交换信息,在命令式的编程中,线程之间的通讯有两种方式:共享内存和消息传递。
2,第二个问题是线程同步,同步指的是程序中用于控制不同线程间的操作发生相对顺序的机制。

java采用的是共享内存模型进行线程间的通讯,在共享内存模型下,同步是显式的进行的,程序员必须显式的指定方法或者代码块之间的互斥关系(锁机制)。

java内存模型的抽象结构:
java中,所有的实例域、静态域、数组都存储在堆内存中,堆内存在线程之间是共享的,也就是抽象模型中的【主内存】,局部变量、方法参数定义和异常处理器不会共享【本地内存】,不涉及内存可见性的问题。

20191210001497\_1.png

在抽象内存模型中,定义了主内存、本地内存、线程三者之间的关系。其中JVM控制着本地内存和主内存之间的交互。

假设线程A和B之间要通讯,一定会经过下面的过程
1,线程A把本地副本刷新到主内存中。
2,jvm设置B线程的本地副本中的变量失效。
3,线程B读取主内存中的变量到本地内存。

jvm就是控制本地内存与主内存之间的交互为程序员提供的内存可见性。

从源代码到指令序列的重排序
执行程序的时候,为了提高性能,编译器和处理器常常会对指令做重排序,重排序分为三种类型。
1,编译器优化的重排序
2,指令级并行的重排序
3,内存系统的重排序

20191210001497\_2.png

jvm通过禁止特定类型的编译器重排序和处理器的重排序,为程序员提供一定的内存可见性。

并发编程模型的分类
现代的处理器都会有写缓冲区来临时保存要写的数据, 相当于把要写的数据队列放在一个缓冲区中, 不影响和堵塞别的读指令操作,写缓冲区有很多好处,但是会带来内存可见性的问题。

假设有两个处理器,一定要注意是两个处理器,不是两个线程。做如下工作的时候。

20191210001497\_3.png

期待的结果当然是x=2 y=1,但是我们可能会得到的结果是x=y=0,原因如下:
20191210001497\_4.png

在写缓冲区没有写的时候,读操作已经执行完成,关键原因是写缓冲区仅仅对自己的处理器可见。

为了保证内存的可见性,java编译器在生成指令序列的时候回插入内存屏障来禁止特定类型的指令重排序,内存指令分为四类:
20191210001497\_5.png

可能表格看起来不舒服,也不好记住
提供一个方法:
java中的内存访问分为两种,一种的是写,也就是刷新到内存[store], 第二种是读,也就是装载[load]。
屏障的类型是有load 和 store组合成的 , 例如load1; loadstore;store2的解释是确保load1先于store2装载,也先于store2后面的指令存储。 第一个字母load 指的是load1和store2的先后顺序, 后面的store指的是load1和load2之后指令集的先后关系。了解这个规律就不怕忘记了。

从上面的图上可以看出StoreLoad是一个全能型的屏障,包含了上面三个屏障的功能。

Happens-before规则
从jdk5开始,java使用的是jsr-133内存模型,jsr-133模型使用happens-before概念阐述内存的可见性。
happens-before规则:
1,程序顺序原则: 在一个线程中,前面的代码happens-before于后面的代码。
2,监视器锁原则: 对于一个锁的解锁happens-before于随后对这个锁的加锁。
3,volatile变量规则: 对于一个volatile域的写happens-before语后续对这个域的读
4,传递性原则:Ahappens-beforeB, Bhappens-beforeC 则Ahappens-beforeC

需要注意的是happens-before规则并不是指的是前一个操作必须早后一个操作之前执行,而是指的是前一个操作必须对后一个操作可见。

点赞(0)
版权归原创作者所有,任何形式转载请联系作者; Java 技术驿站 >> java并发编程的艺术【三】-【一】java内存模型基础

相关推荐