1. 引言
并发的两个关键问题:
- 线程之间以何种机制来交换信息?
- 线程以何种机制来控制不同线程间操作发生的相对顺序?
2. 并发模型
有两种并发编程模型可以解决以上两个问题:
- 消息传递并发模型
- 共享内存并发模型
两并发模型的区别如下:
通信 | 同步 | |
---|---|---|
消息传递并发模型 | 线程之间没有公共状态,线程之间的通信必须通过发送消息来显示进行通信 | 发送消息天然同步,因为发送消息总是在接受消息之前,因此它们的同步是隐式的。 |
共享内存并发模型 | 线程之间的共享程序的公共状态,通过读写内存中的公共状态进行隐式通信。 | 必须显示指定某段代码需要在线程之间互斥执行,因此同步是显示的。 |
Java 中使用的是共享内存并发模型。
3. 内存模型
3.1 内存划分
对于每一个线程来讲,栈是私有的,堆是共享的。
也就是说在栈中的局部变量,方法定义参数,异常处理器参数不会在线程中共享,也就没有下文所讲的内存可见性问题,也不受内存模型影响。
所以,内存可见性针对的是堆中的变量(共享变量)。
3.2 内存可见性
现代计算机为了高效,往往会在闪存中缓存共享变量,因为闪存的速度比内存快得多且更加高效。
线程之间的共享变量存储在主内存(闪存)中,每个线程都拥有自己的本地内存,存储了该线程读写变量的副本。
本地内存是Java内存模型的一个抽象概念,它并不真实存在。
本地内存覆盖了缓存,写缓冲区,寄存器等。
Java 的线程之间通信由 JMM(Java Memory Manager)控制,从抽象的角度来说 JMM 定义了线程和主内存之间的抽象关系。
图中所需要关注的点:
- 所有的共享变量存储在主内存中
- 所有的线程都保存了一份共享变量的副本
如果线程A 与 线程B 要通信的话,必须经历如下两个步骤:
- 线程A 将本地内存更新过的值刷新(同步)到主内存中
- 线程B 到主内存中读取 线程A 已更新过的共享变量
所以,线程之间通信必须通过主内存。
JMM 规定,线程对共享变量的操作必须在自己的本地内存中进行,不能直接从主内存中读写。
线程B 并不是直接从主内存中读取变量的值,而是先在本地内存找到这个共享变量,如果发现这个共享变量已经被更新了,那么 线程B 的本地内存就会从主内存中读取这个共享变量的新值,并拷贝到本地内存中,最后 线程B 再读取本地内存中的新值。
那么如何知道这个共享变量是否已被其它线程更新了呢?这就是前文所提到的 JMM 功劳了,也是 JMM 存在的重要性之一。JMM 通过控制主内存与每个线程的本地内存之间的交互,来提供内存可见性。
3.3 JMM与内存划分的区别与联系
区别:
概念上的不同,JMM是抽象的,它用来描述一种规则,通过这个规则可以用来控制变量的访问方式。围绕原子性,有序性,可见性等展开的。而 Java 运行内存划分是具体的,是 JMM 运行 Java 程序时必要的内存划分。
联系:
都存在私有数据区域和共享数据区域。⼀般来说,JMM中的主内存属于共享数据区域,他是包含了堆和⽅法区;同样,JMM中的本地内存属于私有数据区域,包含了程序计数器、本地⽅法栈、虚拟机栈。
实际上,他们表达的是同⼀种含义。