Java并发编程(二):聊一聊Java内存模型

前言

多线程任务处理在现代计算机系统中几乎已经是一项必备的功能了。在许多场景下,让计算机同时处理几件事情,不仅是因为计算机的运算能力比之前强大了,还有一个很重要的原因是计算机的运算速度与存储设备的速度相差太多,大量的时间都花费在磁盘I/O、网络通信或者数据库访问上。如果不想让处理器在大部分时间都处于等待其他资源的空闲状态,就要使用一些手段去把处理器的运算能力压榨出来,否则就会造成性能的浪费。其实这个也是采用多线程的一个原因,通过线程切换来提高性能。

概念

Java内存模型(Java Memory Model,JMM,以下简称JMM)和JVM的内存模型不相同,Java内存模型其实就是一个规范,屏蔽了各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果。

Java内存模型的抽象结构

JMM的目的就是定义程序中各种变量的访问规则,即关注在虚拟机中把变量值存储到内存和从内存中取出这样的细节。在Java中,所有实例域、静态域和数组元素都存储在堆内存中,堆内存在线程之间共享。

Java线程之间的通信由JMM控制,JMM决定一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地 内存(Local Memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的 一个抽象概念,并不真实存在。 Java内存模型的抽象示意图如下:

线程之间的通信要通过主内存来进行,每个线程的本地内存保存了一份共享变量的副本,那么线程之间的通信过程如下:

  • 线程A把本地内存中修改过的共享变量刷新到主内存中;
  • 线程B到主内存中去读取线程A更新过的共享变量。 经过上述两个过程完成了线程之间的通信。 线程之间未发生通信时的示意图如下:

当线程发生通信时,示意图如下:

通过上面示意图我们就能看出线程之间是如何进行通信的,可以看到通信过程中必须经过主内存,JMM通过控制主内存与每个线程的本地内存之间的交互,来为Java程序员提供内存可见性保证。

内存间交互操作

Java内存模型中定义了八种操作来实现主内存与工作内存中的交互协议:

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

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

  • read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以 便随后的load动作使用。

  • load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的 变量副本中。

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

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

  • store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随 后的write操作使用。

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

如果要把一个变量从主内存拷贝到工作内存,那就要按顺序执行read和load操作,如果要把变量从工作内存同步回主内存,就要按顺序执行store和write操作。注意,Java内存模型只要求上述两个操作必须按顺序执行,但不要求是连续执行。也就是说read与load之间、store与write之间是可插入其他指令的,如对主内存中的变量a、b进行访问时,一种可能出现的顺序是read a、read b、load b、load a。除此 之外,Java内存模型还规定了在执行上述8种基本操作时必须满足如下规则:

  • 不允许read和load、store和write操作之一单独出现,即不允许一个变量从主内存读取了但工作内 存不接受,或者工作内存发起回写了但主内存不接受的情况出现。

  • 不允许一个线程丢弃它最近的assign操作,即变量在工作内存中改变了之后必须把该变化同步回 主内存。

  • 不允许一个线程无原因地(没有发生过任何assign操作)把数据从线程的工作内存同步回主内存 中。

  • 一个新的变量只能在主内存中“诞生”,不允许在工作内存中直接使用一个未被初始化(load或 assign ) 的 变 量 , 换 句 话 说 就 是 对 一 个 变 量 实 施 use 、 store 操 作 之 前 , 必 须 先 执 行 assign 和 load 操 作 。

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

  • 如果对一个变量执行lock操作,那将会清空工作内存中此变量的值,在执行引擎使用这个变量 前,需要重新执行load或assign操作以初始化变量的值。

  • 如果一个变量事先没有被lock操作锁定,那就不允许对它执行unlock操作,也不允许去unlock一个 被其他线程锁定的变量。

  • 对一个变量执行unlock操作之前,必须先把此变量同步回主内存中(执行store、write操作)。

Java内存模型三大特性

1. 原子性

由Java内存模型来直接保证的原子性变量操作包括 read 、load 、assign 、use 、 store 和 write 这六个 ,我们大致可以认为,基本数据类型的访问、读写都是具备原子性的,但是double、long有点例外,在32位系统上读写64位的double、long变量时,因为每次只能读写32位长度,所以一个变量要分两次进行读写,这个就不是原子操作。简单来说,原子操作就是不可再分的操作。double、long变量在分两次进行读写的过程中,有可能因为线程切换导致某个线程读取到的变量发生错误。

2. 有序性

有序性是指:在本线程内观察,所有操作都是有序的。在一个线程观察另一个线程,所有操作都是无序的,无序是因为发生了指令重排序。在 Java 内存模型中,允许编译器和处理器对指令进行重排序,重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。

Java语言提供了volat ile和sy nchroniz ed两个关键字来保证线程之间操作的有序性,volat ile关键字本 身就包含了禁止指令重排序的语义,而synchronized则是由“一个变量在同一个时刻只允许一条线程对 其进行lock操作”这条规则获得的,这个规则决定了持有同一个锁的两个同步块只能串行地进入。

3. 可见性

可见性就是指当一个线程修改了共享变量的值时,其他线程能够立即得知这个修改。Java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值来实现可见性的。JMM 内部的实现通常是依赖于所谓的内存屏障,通过禁止某些重排序的方式,提供内存可见性保证,也就是实现了各种 happens-before 规则。与此同时,更多复杂度在于,需要尽量确保各种编译器、各种体系结构的处理器,都能够提供一致的行为。

happens-before先行发生原则

  • 程序次序规则(Program Order Rule):在一个线程内,按照控制流顺序,书写在前面的操作先行发生于书写在后面的操作。注意,这里说的是控制流顺序而不是程序代码顺序,因为要考虑分支、循 环等结构。

  • 管程锁定规则(Monitor Lock Rule):一个unlock操作先行发生于后面对同一个锁的lock操作。这里必须强调的是“同一个锁”,而“后面”是指时间上的先后。

  • volatile变量规则(Volatile Variable Rule):对一个volatile变量的写操作先行发生于后面对这个变量的读操作,这里的“后面”同样是指时间上的先后。

  • 线程启动规则(Thread Start Rule):Thread对象的start()方法先行发生于此线程的每一个动作。

  • 线程终止规则(Thread Termination Rule):线程中的所有操作都先行发生于对此线程的终止检测,我们可以通过T hread::join()方法是否结束、T hread::isAlive()的返回值等手段检测线程是否已经终止执行。

  • 线程中断规则(Thread Interruption Rule):对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过T hread::interrupted()方法检测到是否有中断发生。

  • 对象终结规则(Finalizer Rule):一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始。

  • 传递性(Transitivity):如果操作A先行发生于操作B,操作B先行发生于操作C,那就可以得出操作A先行发生于操作C的结论。

写在最后

了解Java内存模型对我们之后解决并发问有很大帮助,所以看不懂的话多看两遍吧。

参考资料
  • 《Java并发编程的艺术》

  • 《深入理解Java虚拟机》

欢迎扫码或搜索关注公众号:Android进阶之旅

猜你喜欢

转载自juejin.im/post/5e60aa6c6fb9a07cda09796b
今日推荐