Java多线程内存模型


JMM的基本概念

Java作为平台无关性语言,JLS(Java语言规范)定义了一个统一的内存管理模型JMM(Java Memory Model)。JMM规定了jvm内存分为主内存和工作内存 ,主内存存放程序中所有的类实例、静态数据等变量,是多个线程共享的,而工作内存存放的是该线程从主内存中拷贝过来的变量以及访问方法所取得的局部变量,是每个线程私有的其他线程不能访问。每个线程对变量的操作都是以先从主内存将其拷贝到工作内存再对其进行操作的方式进行,多个线程之间不能直接互相传递数据通信,只能通过共享变量来进行。
2
从上图来看,线程1与线程2之间如要通信的话,必须要经历下面2个步骤:

首先,线程1把本地工作内存中更新过的共享变量刷新到主内存中去。
然后,线程2到主内存中去读取线程1之前已更新过的共享变量。
典型的高并发引起的问题就存在由于线程读取到的数据还没有从另外的线程刷新到主内存中而引起的数据不一致问题。


主内存与工作内存的数据交互

JLS一共定义了8种操作来完成主内存与线程工作内存的数据交互:

lock:把主内存变量标识为一条线程独占,此时不允许其他线程对此变量进行读写
unlock:解锁一个主内存变量
read:把一个主内存变量值读入到线程的工作内存
load:把read到变量值保存到线程工作内存中作为变量副本
use:线程执行期间,把工作内存中的变量值传给字节码执行引擎
assign:字节码执行引擎把运算结果传回工作内存,赋值给工作内存中的结果变量
store:把工作内存中的变量值传送到主内存
write:把store传送进来的变量值写入主内存的变量中

使用标准的操作再来重现一下上方的2个线程之间的交互流程则是这样的:
线程1从主内存read一个值为0的变量x到工作内存
使用load把变量x保存到工作内存作为变量副本
将变量副本x使用use传递给字节码执行引擎进行x++操作
字节码执行引擎操作完毕后使用assign将结果赋值给变量副本
使用store把变量副本传送到主内存
使用write把store传送的数据写到主内存
线程2从主内存read到x,然后load–>use–>assign–>store–>write

另外使用这8种操作也有一些规则:
read 和 load必须以组合的方式出现,不允许一个变量从主内存读取了但工作内存不接受情况出现
store和write必须以组合的方式出现,不允许从工作内存发起了存储操作但主内存不接受的情况出现
工作内存的变量如果没有经过 assign 操作,不允许将此变量同步到主内存中
在 use 操作之前,必须经过 load 操作
在 store 操作之前,必须经过 assign 操作
unlock 操作只能作用于被 lock 操作锁定的变量
一个变量被执行了多少次 lock 操作就要执行多少次 unlock 才能解锁
一个变量只能在同一时刻被一条线程进行 lock 操作
执行 lock 操作后,工作内存的变量的值会被清空,需要重新执行 load 或 assign 操作初始化变量的值
对一个变量执行 unlock 操作之前,必须先把此变量同步回主内存中


多线程中的原子性、可见性、有序性

原子性:关于原子性的定义可以参考我的上篇博客《浅谈数据库事务》。在JLS中保证原子性的操作包括read、load、assign、use、store和write。基本数据类型(除了long 和double)操作都具有原子性。
如果需要更大范围的原子性操作的时候,可以使用lock和unlock操作来完成这种需求。
可见性:是指当一个线程修改了共享变量的值,其他线程是否能够立即得知这个修改。
由上方JMM的概念得知,线程操作数据是在工作内存的,当多个线程操作同一个数据的时候很容易读取到还没有被write到主内存变量的值。
Java是如何保证可见性的:volatile、synchronized、final关键字
有序性:在并发时,程序的执行可能会出现乱序。给人的直观感觉就是:写在前面的代码,会在后面执行。有序性问题的原因是因为程序在执行时,可能会进行指令重排,重排后的指令与原指令的顺序未必一致。关于指令重排会在下方讲。


指令重排

int a=1;
int b=2;
int c=3;
int d=4;

你能说出上方这段代码的执行顺序么?其实我们可能理所当然的以为它会从上往下顺序执行。事实上,在实际运行时,为了优化指令的执行顺序等,代码指令可能并不是严格按照代码语句顺序执行的。上方的代码执行顺序可能完全反过来,这个就是指令重排。
不过呢,指令重排也不是可以随意重排的,它需要遵守一定的规则:
程序顺序规则:一个线程内保证语义的正确性。
锁规则:解锁肯定先于随后的加锁前。
volatile规则:对一个volatile的写,先于volatile的读。
传递性:如果A 先于 B,且B 先于 C,那么A 肯定先于 C。
start()规则:线程的start()操作先于线程的其他操作。
join()规则:线程的所有操作先于线程的关闭。
程序中断规则:线程的中断先于被中断后执行的代码。
对象finalize规则:一个对象的初始化完成先于finalize()方法。


volatile关键字

volatile关键字旨在告诉虚拟机在这个地方要注意不能随意的进行指令重排,而虚拟机看到一个变量被volatile修饰以后就会采用一些特殊的手段来保证变量的可见性。不过要注意的是volatile关键字不能保证原子性。

原文地址:https://blog.51cto.com/12980017/2366331

时间: 2024-10-14 02:05:17

Java多线程内存模型的相关文章

Java虚拟机内存模型及垃圾回收监控调优

Java虚拟机内存模型及垃圾回收监控调优 如果你想理解Java垃圾回收如果工作,那么理解JVM的内存模型就显的非常重要.今天我们就来看看JVM内存的各不同部分及如果监控和实现垃圾回收调优. JVM内存模型         正如你上图所看到的,JVM内存可以划分为不同的部分,广义上,JVM堆内存可以划分为两部分:年轻代和老年代(Young Generation and Old Generation) 年轻代(Young Generation) 年轻代用于存放由new所生成的对象.当年轻代空间满时,

java线程内存模型,线程、工作内存、主内存

转自:http://rainyear.iteye.com/blog/1734311 java线程内存模型 线程.工作内存.主内存三者之间的交互关系图: key edeas 所有线程共享主内存 每个线程有自己的工作内存 refreshing local memory to/from main memory must  comply to JMM rules 产生线程安全的原因 线程的working memory是cpu的寄存器和高速缓存的抽象描述:现在的计算机,cpu在计算的时候,并不总是从内存读

浅析Java的内存模型

一.前言 ??之前过年在家,空闲时间比较多,想要了解一下JVM相关的内容,于是买了<深入理解Java虚拟机>这本书,过了一遍其中的基础知识.时隔多日,都忘得差不多了.为了重新捡起来,我决定复习一遍,并编写相关的系类博文加深印象,这是第一篇,来讲一讲JVM最基础的内容--Java的内存模型. 二.正文 ?2.1 Java内存分布 ??Java的内存主要分为五个部分: 程序计数器: Java虚拟机栈: 本地方法栈: 堆内存: 方法区: ??具体结构如下图所示: 2.2 程序计数器 ??首先看第一部

java String 内存模型

关于java的内存模型,参照以下的一篇文章: https://isudox.com/2016/06/22/memory-model-of-string-in-java-language/

Java(1):多线程内存模型和状态切换

线程的内存模型 32位操作系统的寻址空间为2的32次方,也就是4GB的寻址空间:系统在这4GB的空间里划分出1GB的空间给系统专用,称作内核空间,具有最高权限:剩下3GB的空间为用户空间(一般JVM的可用内存最大只能是2GB),只能访问当前线程划分的内存地址.用户线程需要访问硬件资源的时候需要委托内核线程进行访问,这就涉及到CPU上下文在用户模式和内核模式的切换.因此在使用线程或者进程的时候需要尽量避免不必要的用户模式和内核模式的切换. 进程是资源管理的最小单位,线程是CPU调度的最小单位.线程

关Java的内存模型(JMM)

JMM的关键技术点都是围绕着多线程的原子性.可见性和有序性来建立的 一.原子性(Atomicity) 原子性是指一个操作是不可中断的.即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰. 比如,对于一个静态全局变量int i,两个线程同时对它赋值,线程A给他赋值1,线程B给它赋值为-1.那么不管这2个线程以何种方式.何种步调工作,i的值要么是1,要么是-1.线程A和线程B之间是没有干扰的.这就是原子性的一个特点,不可被中断. 但如果我们不使用int型而使用long型的话,可能

浅谈Java的内存模型以及交互

本文的内存模型只写虚拟机内存模型,物理机的不予描述. Java内存模型 在Java中,虚拟机将运行时区域分成6中,如下图:              程序计数器:用来记录当前线程执行到哪一步操作.在多线程轮换的模式中,当当前线程时间片用完的时候记录当前操作到哪一步,重新获得时间片时根据此记录来恢复之前的操作. 虚拟机栈:这就是我们平时所说的栈了,一般用来储存局部变量表.操作数表.动态链接等. 本地方法栈:这是另一个栈,用来提供虚拟机中用到的本地服务,像线程中的start方法,JUC包里经常使用的

(一)java的内存模型

程序计数器(私有) 程序计数器:"是一个非常小的内存空间,用来保证程序依次执行",它可以看作是当前线程所执行的字节码的行号指示器 由于java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在切出后切回的时候需要一个标识. 栈 (私有) "基本类型.运算.方法服务.指向堆内存的指针" 虚拟机栈('执行的是java方法服务') 它的生命周期与线程相同.虚拟机栈描述的是Java方法执行的线程内存模型:每个方法被执行的时候,Java虚拟机都会同步创建一

java的内存模型

java内存模型 Java虚拟机规范中试图定义一种Java内存模型(Java Memory Model,JMM)来屏蔽掉各种硬件和操作系统的访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果.在此之前,主流程序语言(如C/C++等)直接使用物理硬件和操作系统的内存模型,因此,会由于不同平台上内存模型的差异,有可能导致程序在一套平台上并发完全正常,而在另外一套平台上并发访问却经常出错,因此在某些场景下就不许针对不同的平台来编写程序.Java内存模型即要定义得足够严谨,才能让Jav