并发编程-Java内存模型

导读:本篇文章讲解 并发编程-Java内存模型,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

将之前看过的关于并发编程的东西总结记录一下,本文简单记录Java内存模型的相关知识。

1. 并发编程两个关键问题

并发编程中,需要处理两个关键问题:线程之间如何通信及线程之间如何同步。

(1)在命令式编程(命令式编程侧重于告诉计算机先做什么后做什么,与声明式只告诉做什么,不告诉怎么做不同)中,线程间的通信机制有两种:共享内存和消息传递。

  ① 在共享内存的并发模型中,线程之间共享程序的公共状态,通过读写内存的公共状态进行隐式通信;

  ② 在消息传递的并发模型中,线程间没有公共状态,其通过发生消息进行显式通信。

(2)线程同步是用于控制不同线程间操作发生的相对顺序,在共享内存并发模型中,同步是显式进行的,如通过synchronized控制线程互斥的访问某方法或代码块;在消息传递的并发模型中,消息发送再接收之前,同步是隐式进行的。

Java的并发采用的是共享内存模型,通信是隐式的,同步是显式的。

2. Java内存模型

(1)计算机硬件结构

  介绍Java内存模型之前,先看下目前计算机硬件架构,如下图(摘自Java内存模型,具体介绍可参考该文章)。

并发编程-Java内存模型

  由于计算机的存储设备与处理器的运算速度几个数量级的差距,所以现代计算机系统都引入一层读写速度与处理器接近的高速缓存(CPU Cache)作为内存与处理器之间的缓冲。将运算需要使用的数据复制到缓存中,加快运算速度,当运算结束后再从缓存同步到内存中,这样处理器不需要等待缓慢的内存IO。

   在此情况下,引入了新的问题:缓存一致性。在多处理器系统中,每个处理器都有自己的高速缓存,它们共享同意主内存(Main Memory)。为解决缓存一致性的问题,需要各处理器访问缓存时都遵循一些协议,在读写时要根据协议来进行操作,如MSI协议(监听一致性协议通过利用总线将处理器核的私有Cache 和主存储器连接在一起,每一个私有 Cache 中的控制器会时刻侦听总线上的消息,并根据消息的类型做出相应的操作—百度百科)等。

(2)Java 内存模型的抽象结构

  Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将共享变量存储到内存和从内存取出的机制,这些共享变量包括实例字段、静态字段和构成数组对象的元素,可以认为都存储在堆内存中。

  Java内存模型的抽象结构如下(摘自Java内存模型的深入理解,该图也是《Java并发编程的艺术》中的):

 

并发编程-Java内存模型

  ① 该模型与上边计算机硬件架构类似,但该主内存是虚拟机的一部分,主要存储所有变量(主要包含堆区部分的变量);

  ② 每条线程都有自己的本地内存(或者叫工作内存),保存主内存汇总变量的副本,线程对变量的操作都是在该内存中进行,可以与处理器高速缓存类比;

  ③ 不同线程间的通信只能通过主内存进行。

(3)内存间的交互

  一个变量从主内存拷贝到本地内存,或从本地内存同步到主内存,需要通过具体的交互协议。Java内存模型定义了8种操作来完成。

lock(锁定) 主内存,将主内存中变量标识为一条线程独占状态
unlock(解锁) 主内存,与lock相反,释放变量的锁
read(读取) 主内存,将变量从主内存传输到本地内存,以便后续的load操作
load(载入) 本地内存,将变量放入本地内存的变量副本中
use(使用) 本地内存,将该变量的值传给执行引擎,用于执行操作,对应使用到该变量的字节码指令
assign(赋值) 本地内存,将从执行引擎接收到的值赋给本地内存的变量,对应赋值字节码指令
store(存储) 本地内存,将本地内存中的变量值传送到主内存,以便后续的write操作
write(写入) 主内存,将从store操作中获取的变量值放入主内存的变量中

  如果要把一个变量从主内存复制到本地内存,需要顺序执行read和load操作,如果把变量从本地内存同步会主内存,需要顺序执行store和write操作。Java内存模型只要求上边两个顺序执行,并不能保证连续执行,即他们之间是可以插入其他命令,非原子操作。这8种基本操作必须满足以下规则:

  ① read和load、store和write不能单独出现

  ② 不允许一个线程丢弃最近assign操作,即本地内存中的改变必须同步回主内存;

  ③ 一个新共享变量只能在主内存中诞生(线程中本地变量可以)

  ④ 一个变量同一时刻只允许一个线程进行lock操作,同一线程可以多次执行lock(对应同一线程可以多次执行synchronized)

  ⑤ 对一个变量执行lock操作时,将会清空本地内存中该变量的值,再执行引擎使用该变量时,需要重新执行load或assign操作初始化变量的值

  ⑥ 对变量执行lock操作,就不可以执行unlock操作

  ⑦ 将一个变量unlock之前,必须先把该变量同步回主内存(执行store和write操作)

(4)原子性、可见性和有序性

  Java内存模型具有原子性、可见性和有序性等3个特性。

  ① 原子性,上边描述的8种操作都具有原子性,如需要更大范围的原子性,可以使用lock和unlock操作。虽然该指令并没有开放给用户使用,但提高了更高层次的字节码指令monitorenter和monitorexit,其对应Java代码中锁定代码块的synchronized关键字。

  ② 可见性,当一个线程对共享变量修改后,其他线程可以立即得到该值。Java内存模型是通过将修改后的变量值同步回主内存,在线程读取前从主内存刷新该变量新值实现可见性,对普通变量和volatile变量都是如此,只是volatile变量的特殊规则保证新值能立即同步到主内存,每次使用前立即从主内存刷新,故volatile能保证多线程操作时变量的可见性,具体volatile讲述见后续文章。

  除了volatile之外,还有两个关键字能实现可见性,synchronized和final,前者通过对线程顺序执行实现,final修饰的字段在构造器中初始化后,并且构造器没有this引用逃逸(把this引用传递出去),其他线程就可以看到final字段的值。

  ③ 有序性,如果在本线程内观察,所有操作都是有序的,即无论编译器或处理器对指令怎么进行重排序,执行结果不变,遵从as-if-serial语义;如果一个线程观察另一个线程,所有操作都是无序的,是指存在“指令重排序”和“本地内存与主内存同步延迟”的现象。

  Java提供了volatile和synchronized保证线程之间的有序性,前者禁止指令重排序,后者由上边规则4获得。

3. 总结

1. Java线程间通信是通过共享内存的方式进行的,通信是隐式地通过更新主内存实现;线程间同步通过显式地通过互斥等方式实现

2. 计算机硬件架构中引入高速缓存以加速处理器,通过相关协议解决多处理器中“缓存一致性”的问题

3. Java内存模型与家算计硬件架构类似,主内存中存储共享变量,本地内存或工作内存中存储变量拷贝,用于各线程计算,可以通过锁实现同步

4. 提供8种操作用于主内存与本地内存的交互,并且必须遵循一定的规则使用

5. Java内存弄下具有原子性、可见性和有序性。

6. 另外,Java内存模型中还存在“先行发生”(happen-before)的原则,其是内存模型中定义的两项操作之间的偏序关系,如说A先行发生于B,则A操作的结果B可以观察到。如Monitor Lock Rule、volatile变量写操作先行于读操作、线程终结、对象终结等。是判断数据是否存在竞争、线程是否安全的主要依据。

4.参考

《Java并发编程的艺术》

《深入理解Java虚拟机》

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/16226.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!