一.java并发基础
在并发编程中存在两个关键问题①线程之间如何通信 ②线程之间如何同步。
通信
通信是指线程之间以何种机制来交换信息。在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递。
在共享内存的并发模型里,线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信。
在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过明确的发送消息来显式进行通信。
同步
同步是指程序用于控制不同线程之间操作发生相对顺序的机制。
在共享内存并发模型里,同步是显式进行的。程序员必须显式指定某个方法或某段代码需要在线程之间互斥访问。
在消息传递的并发模型里,由于消息的发送必须在消息的接收之前, 因此同步是隐式进行的。
Java并发采用的是共享内存模型,通信隐式进行;同步显示指定。
二.内存模型
Java内存模型JMM(Java Memory Model)主要目标是定义程序中各个变量(非线程私有)的访问规则,即在虚拟机中将变量存储到内存和从内存取出变量这样的底层细节。Java中每个线程都有自己私有的工作内存。工作内存保存了被该线程使用的变量的主内存副本拷贝,线程对变量的读写操作都必须在工作内存进行,无法直接读写主内存中的变量。两个线程无法直接访问对方的工作内存。
- 每一个线程有一个工作内存和主存独立
- 工作内存存放主存中变量的值的拷贝
(1)当数据从主内存复制到工作存储时,必须出现两个动作:第一,由主内存执行的读(read)操作;第二,由工作内存执行的相应的load操作;当数据从工作内存拷贝到主内存时,也出现两个操作:第一个,由工作内存执行的存储(store)操作;第二,由主内存执行的相应的写(write)操作。
(2) 每一个操作都是原子的,即执行期间不会被中断。
(3)对于普通变量,一个线程中更新的值,不能马上反应在其他变量中,如果需要在其他线程中立即可见,需要使用 volatile 关键字
内存模型的抽象:
在 Java 中,所有实例域、静态域 和数组元素存储在堆内存中,堆内存在线程之间共享。局部变量、方法定义参数 和 异常处理器参数 不会在线程之间共享,它们不会有内存可见性问题,也不受内存模型的影响。Java 线程之间的通信由 Java 内存模型(JMM)控制。JMM 决定了一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来看,JMM 定义了线程与主内存之间的抽象关系:线程之间的共享变量存储在主内存中,每一个线程都有一个自己私有的本地内存,本地内存中存储了该变量以读/写共享变量的副本。本地内存是 JMM 的一个抽象概念,并不真实存在。
JMM抽象示意图如下:
从上图来看,如果线程 A 和线程 B 要通信的话,要如下两个步骤:
1、线程 A 需要将本地内存 A 中的共享变量副本刷新到主内存去
2、线程 B 去主内存读取线程 A 之前已更新过的共享变量
步骤示意图:
举个例子:
本地内存 A 和 B 有主内存共享变量 X 的副本。假设一开始时,这三个内存中 X 的值都是 0。线程 A 正执行时,把更新后的 X 值(假设为 1)临时存放在自己的本地内存 A 中。当线程 A 和 B 需要通信时,线程 A 首先会把自己本地内存 A 中修改后的 X 值刷新到主内存去,此时主内存中的 X 值变为了 1。随后,线程 B 到主内存中读取线程 A 更新后的共享变量 X 的值,此时线程 B 的本地内存的 X 值也变成了 1。
整体来看,这两个步骤实质上是线程 A 再向线程 B 发送消息,而这个通信过程必须经过主内存。JMM 通过控制主内存与每个线程的本地内存之间的交互,来为 Java 程序员提供内存可见性保证。
内存间通信的指令: