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

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

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

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

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

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

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

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

2. Java内存模型

(1)计算机硬件结构

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

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

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

(2)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虚拟机》

并发编程-Java内存模型的更多相关文章

  1. Java并发编程-Java内存模型

    JVM内存结构与Java内存模型经常会混淆在一起,本文将对Java内存模型进行详细说明,并解释Java内存模型在线程通信方面起到的作用. 我们常说的JVM内存模式指的是JVM的内存分区:而Java内存 ...

  2. 并发编程 —— Java 内存模型总结图

    关于 Java 内存模型的类似思维导图. 如有错误,还请指正.

  3. 高效并发一 Java内存模型与Java线程(绝对干货)

    高效并发一 Java内存模型与Java线程 本篇文章,首先了解虚拟机Java 内存模型的结构及操作,然后讲解原子性,可见性,有序性在 Java 内存模型中的体现,最后介绍先行发生原则的规则和使用. 在 ...

  4. x86-TSO : 适用于x86体系架构并发编程的内存模型

    Abstract : 如今大数据,云计算,分布式系统等对算力要求高的方向如火如荼.提升计算机算力的一个低成本方法是增加CPU核心,而不是提高单个硬件工作效率. 这就要求软件开发者们能准确,熟悉地运用高 ...

  5. Java并发编程、内存模型与Volatile

    http://www.importnew.com/24082.html  volatile关键字 http://www.importnew.com/16142.html  ConcurrentHash ...

  6. JAVA多线程编程——JAVA内存模型

    一.何为“内存模型” 内存模型描述了程序中各个变量(实例域.静态域和数组元素)之间的关系,以及在实际计算机系统中将变量存储到内存和从内存中取出变量这样的底层细节,对象最终是存储在内存里面的,但是编译器 ...

  7. 【深入理解JAVA虚拟机】第5部分.高效并发.1.Java内存模型与线程。

    1.概述 摩尔定律:描述处理器晶体管数量与运行效率之间的发展关系.Amdahl定律:通过系统中并行化与串行化的比重来描述多处理器系统能获得的运算加速能力. 从摩尔定律到Amdahl定律的转变,代表了近 ...

  8. 【并发编程】- 内存模型(针对JSR-133内存模型)篇

    并发编程模型 1.两个关键问题 1)线程之间如何通信 共享内存 程之间共享程序的公共状态,通过写-读内存中的公共状态进行隐式通信 消息传递 程之间没有公共状态,线程之间必须通过发送消息来显式进行通信 ...

  9. 【死磕Java并发】-----Java内存模型之happend-before

    在上篇博客([死磕Java并发]-–深入分析volatile的实现原理)LZ提到过由于存在线程本地内存和主内存的原因,再加上重排序,会导致多线程环境下存在可见性的问题.那么我们正确使用同步.锁的情况下 ...

随机推荐

  1. [转帖]安装prometheus+grafana监控mysql redis kubernetes等

    安装prometheus+grafana监控mysql redis kubernetes等 https://www.cnblogs.com/sfnz/p/6566951.html plug 的模式进行 ...

  2. java:线上问题排查常用手段(转)

    出处:java:线上问题排查常用手段 一.jmap找出占用内存较大的实例 先给个示例代码: import java.util.ArrayList; import java.util.List; imp ...

  3. Django+celery+rabbitmq实现邮件发送

    一.环境 1.pip包 amqp==2.4.2 anyjson==0.3.3 billiard==3.6.0.0 celery==4.3.0 Django==2.2 dnspython==1.16.0 ...

  4. 怎样理解Node接口 / ParentNode接口 / ChildNode接口

    ParentNode 和 ChildNode可以理解为是Node的子集, 它对一些具有父节点或子节点的节点提供了一些额外的方法和属性, 比如: 1. 继承了ParentNode的接口有: 元素节点 / ...

  5. C#int类型 转Byte[]

    方法1:使用左移和右移 int转化为byte[]:   public  byte[] intToBytes(int value)         {             byte[] src = ...

  6. linq to sql之Distinct

    Distict用来排除相同序列中元素的,对于基础类型,可以直接使用Distinct,如:int[] a = {1, 2, 2, 3, 3, 3, 4};var reslut = a.Distinct( ...

  7. vscode快捷操作

    Ctrl + `                     打开或关闭终端 Ctrl + Shift + n         打开或关闭新窗口 Ctrl + Shift + f 打开视图,显示编辑器左侧 ...

  8. 卡尔曼(Kalman)滤波及十种数据采集滤波的方法和编程实例

    卡尔曼(Kalman)滤波:https://blog.csdn.net/CSDN_X_W/article/details/90289021 十种数据采集滤波的方法和编程实例:https://wenku ...

  9. 退居三线iOS开发的自主开发历程

    忙前忙后,一切终将步入正轨,在忙也要抽出时间思考自己的事情 推荐一篇简书(https://www.jianshu.com/u/8367278ff6cf)讲解很官方 Metal体验 学习了一些基础的视频 ...

  10. Linux系统目录结构和文件基本属性

    一.Linux系统目录结构 二.Linux 文件基本属性 三.touch stat tar 命令 一.Linux系统目录结构 不同颜色文件的含义: inux 文件颜色的含义,蓝色代表目录,绿色代表可执 ...