Java多线程之内存模型

多线程需要解决的问题

线程之间的通信

线程之间的通信

Java内存模型

内存间的交互操作

指令屏障

happens-before规则

指令重排序

从源程序到字节指令的重排序

as-if-serial语义

程序顺序规则

顺序一致性模型

顺序一致性模型特性

顺序一致性模型特性

当程序未正确同步会发生什么

参考资料

多线程需要解决的问题

在多线程编程中,线程之间如何通信和同步是一个必须解决的问题:

线程之间的通信:

线程之间有两种通信的方式:消息传递和共享内存

共享内存:线程之间共享程序的公共状态,通过读——写修改公共状态进行隐式通信。如上面代码中的num和Lock可以被理解为公共状态

消息传递:线程之间没有公共状态,必须通过发送消息来进行显示通信
在java中,线程是通过共享内存来完成线程之间的通信

线程之间的通信:

同步指程序中永固空值不同线程间的操作发生的相对顺序的机制

共享内存:同步是显示进行的,程序员需要指定某个方法或者某段代码需要在线程之间互斥执行。如上面代码中的Lock加锁和解锁之间的代码块,或者被synchronized包围的代码块

消息传递:同步是隐式执行的,因为消息的发送必然发生在消息的接收之前,例如使用Objetc#notify(),唤醒的线程接收信号一定在发送唤醒信号的发送之后。

Java内存模型

在java中,所有的实例域,静态域、数组都被存储在堆空间当中,堆内存在线程之间共享。

所有的局部变量,方法定义参数和异常处理器参数不会被线程共享,在每个线程栈中独享,他们不会存在可见性和线程安全问题。

从Java线程模型(JMM)的角度来看,线程之间的共享变量存储在主内存当中,每个线程拥有一个私有的本地内存(工作内存)本地内存存储了该线程读——写共享的变量的副本。
JMM只是一个抽象的概念,在现实中并不存在,其中所有的存储区域都在堆内存当中。JMM的模型图如下图所示:

Java多线程之内存模型

而java线程对于共享变量的操作都是对于本地内存(工作内存)中的副本的操作,并没有对共享内存中原始的共享变量进行操作;

以线程1和线程2为例,假设线程1修改了共享变量,那么他们之间需要通信就需要两个步骤:

线程1本地内存中修改过的共享变量的副本同步到共享内存中去

线程2从共享内存中读取被线程1更新过的共享变量
这样才能完成线程1的修改对线程2的可见。

内存间的交互操作

为了完成这一线程之间的通信,JMM为内存间的交互操作定义了8个原子操作,如下表:

操作 作用域 说明
lock(锁定)   共享内存中的变量   把一个变量标识为一条线程独占的状态  
unlock(解锁)   共享内存中的变量   把一个处于锁定的变量释放出来,释放后其他线程可以进行访问  
read(读取)   共享内存中的变量   把一个变量的值从共享内存传输到线程的工作内存。供随后的load操作使用  
load(载入)   工作内存   把read操作从共享内存中得到的变量值放入工作内存的变量副本当中  
use(使用)   工作内存   把工作内存中的一个变量值传递给执行引擎  
assign(赋值)   工作内存   把一个从执行引擎接受到的值赋值给工作内存的变量  
store(存储)   作用于工作内存   把一个工作内存中的变量传递给共享内存,供后续的write使用  
write(写入)   共享内存中的变量   把store操作从工作内存中得到的变量的值放入主内存  

JMM规定JVM四线时必须保证上述8个原子操作是不可再分割的,同时必须满足以下的规则:

不允许read和load、store和write操作之一单独出现,即不允许只从共享内存读取但工作内存不接受,或者工作捏村发起回写但是共享内存不接收

不允许一个线程舍弃assign操作,即当一个线程修改了变量后必须写回工作内存和共享内存

不允许一个线程将未修改的变量值写回共享内存

变量只能从共享内存中诞生,不允许线程直接使用未初始化的变量

一个变量同一时刻只能由一个线程对其执行lock操作,但是一个变量可以被同一个线程重复执行多次lock,但是需要相同次数的unlock

如果对一个变量执行lock操作,那么会清空工作内存中此变量的值,在执行引擎使用这个变量之前需要重新执行load和assign

不允许unlock一个没有被锁定的变量,也不允许unlock一个其他线程lock的变量

对一个变量unlock之前必须把此变量同步回主存当中。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zwszww.html