共享内存并发模型
并发编程模型有两个关键问题,即线程间如何通信,即线程间以何种机制来交换信息;线程间如何同步,即线程以何种机制来控制不同线程间操作发生的相对顺序。
有两个并发模型
- 消息传递并发模型
- 共享内存并发模型
如何通信 | 如何同步 | |
---|---|---|
消息传递并发模型 | 线程间没有公共状态,必须通过发送消息显示地进行通信 | 发送消息总在接受消息之前,同步是隐式的 |
共享内存并发模型 | 线程间共享程序的公共状态,通过读-写内存中的公共状态进行隐式通信 | 必须显示指定某段代码需要在线程之间互斥执行,同步是显示的。 |
Java中使用的是共享内存并发模型。
Java内存模型的抽象结构
运行时内存的划分
运行时数据区:
其中,堆和方法区是所有线程共享的数据区,虚拟机栈、本地方法栈和程序计数器是线程私有的数据区。
对于每一个线程,栈都是私有的,堆都是共有的。
在栈中的变量(局部变量、方法定义参数、异常处理器参数)不会在线程间共享,也就不会有内存可见性问题,不受内存模型的影响。而在堆中的变量是共享的,称为共享变量。
内存可见性针对的是共享变量。
内存不可见性问题
线程间的共享变量存在主内存中,每个线程都有一个自己的本地内存,存储了该线程以读、写共享变量的副本。本地内存是一个抽象概念,并不真实存在,它涵盖了缓存、写缓冲区、寄存器等。
Java线程间的通信由Java内存模型(JMM)控制,JMM定义了线程与主内存间的抽象关系。
- 所有的共享变量都在主内存中
- 每个线程保留了一份该线程使用到的共享变量的副本
- 线程A与线程B要通信的话,必须经过以下步骤:
- 线程A将本地内存A更新过的共享变量刷新到主内存中
- 线程B到主内存中去读取已经更新过的共享变量
线程A无法直接访问线程B的工作内存,线程间通信必须经过主内存。
根据JMM规定,线程对共享变量的所有操作都必须在自己的本地内存中进行,不能直接从主内存读取。
也就是说,线程B不能直接去主内存中读取共享变量的值,而是先在本地内存找到这个共享变量,发现这个共享变量已经被更新过了,然后本地内存B去主内存中读取这个共享变量的新值,再拷贝到本地内存B中,最后线程B再读取本地内存B中的共享变量值。
至于如何知道共享变量已经被更新过了,就是JMM的作用了。JMM通过控制主内存与每个线程的本地内存之间的交互来提供内存可见性保证。
Java中volatile
关键字可以保证多线程操作共享变量的可见性以及禁止指令重排序,synchronized
关键字不仅保证了可见性,也保证了原子性。在更底层,JMM通过内存屏障来实现内存的可见性与禁止重排序,并提出了happens-before。
JMM与Java内存区域划分的区别与联系
区别
JMM是抽象的,用来描述一组规则,通过这个规则来控制各个变量的访问方式,围绕原子性、有序性、可见性等展开的。而Java运行时内存的划分是具体的,时JVM运行Java程序时必要的内存划分。
联系
都存在私有数据区和共享数据区。JMM中的主内存属于共享数据区,包含了堆和方法区;JMM中的本地内存属于私有数据区,包含了虚拟机栈、本地方法栈和程序计数器。