Java多线程中的内存模型

一:现代计算机的高速缓存

在计算机组成原理中讲到,现代计算机为了匹配 计算机存储设备的读写速度 与  处理器运算速度,在CPU和内存设备之间加入了一个名为Cache的高速缓存设备来作为缓冲:将运算需要用到的数据从内存复制到cache中,CPU可以在运算期间对cache进行高速的读写操作,运算结束后在从cache把数据同步回内存。

Cache引出了一个新问题:缓存一致性。每个处理器有自己的cache,而他们又共享一个主内存。当多个处理器的运算任务都设计同一内存区域时,将会导致各自的缓存数据不一致。那么,在各自运算结束后,以谁的缓存数据为准同步回主存呢?此时,就需要 缓存一致性协议 来制定多个处理器对统一内存区域的读写操作了。

除了Cache,为了更好地利用处理器资源,处理器还会对代码进行 乱序执行 ,在最后才把结果重组使得结果与顺序执行一致。

二:Java内存模型的类比

类比与现代计算机的主存与cache,JVM中规定了 所有变量都存储在主内存中(类比计算机的主存),然后每条线程有自己的工作内存(类比每个处理器的cache)。线程的工作内存中保存了该线程需要用到的变量的拷贝值,线程在CPU上运行时都是对自己工作线程中的数据进行读写操作,运行结束之后才把数据同步化主内存中。那么类比于计算机使用 缓存一致性协议 解决缓存一致性问题,JVM中就需要线程同步机制来达到多线程对同一内存区域的读写控制了。

此外,Java编译器为了提高性能,采取了 指令重排序(类比计算机的 乱序执行),若多个线程都有语句对同一内存区域进行操作的话,有可能因为指令重排序而导致结果不符预料。因此,也需要线程同步机制来达到多线程对同一内存区域的读写控制

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

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

1:lock:把主内存变量标识为一条线程独占,此时不允许其他线程对此变量进行读写。

2:unlock:解锁一个主内存变量。

3:read:把一个主内存变量值读入到线程的工作内存,强调的是读入这个过程。

4:load:把read到变量值保存到线程工作内存中作为变量副本,强调的是读入的值的保存过程。

5:use:线程执行期间,把工作内存中的变量值传给字节码执行引擎。

6:assign(赋值):字节码执行引擎把运算结果传回工作内存,赋值给工作内存中的结果变量。

7:store:把工作内存中的变量值传送到主内存,强调传送的过程。

8:write:把store传送进来的变量值写入主内存的变量中,强调保存的过程。

JVM要求以上8个操作都具有原子性,即对数据的读写操作具有原子性。但也有例外,即:long、double的非原子性协定:这两个64位类型的数据的读、写操作各需两次进行,一次读/写 32 位,这两次读/两次写 是不保证原子性的。

四:原子性、可见性、有序性

原子性:基本数据类型的读写操作是原子性的;更大范围的(代码块)的原子性可以用lock、unlock操作来实现(上锁后就只有一个线程来执行了,所以不会被其他线程打断原子操作),表现到代码层面就是使用syncrhoized同步块。

可见性:当一个线程修改了被多线程共享的一个主内存变量值时,其他线程能立刻知道这个修改。

我们在上面可以知道,JVM是通过工作内存中的变量值变化后,把新值同步会主内存,然后其他线程从主内存读取这个新值来实现可见性的。这里有个区别:普通变量的值变化后,不一定会立刻同步会主内存,而是会等线程执行完或者一段时间后才同步会,而且同步回主内存后,其他线程的工作内存也不一定会立刻读取新值。而被volatile关键字修饰的变量,一旦在工作内存中被修改,则立刻同步回主内存,并且其他使用了这个变量的线程的工作内存会立刻从主内存读取新值。而syncrhoized关键字修饰的变量由于一次只能有一个线程能使用,故一次也只能有一个工作线程读写它,所以也能“纵向”地实现可见性。

有序性:多线程之间对共享数据的操作的有序性,可以通过volatile和syncrhoized关键字来保证。volatile关键字禁止了指令重排序,而syncrhoized关键字规定了多个线程每次只能有一个线程对共享数据进行操作。

五:volatile关键字

一个volatile变量具有两种特性:可见性、禁止指令重排序。但是,volatile不具备原子性!原因是volatile变量的值可以被多线程交替修改,而修改包括了read、load、use、store、write等过程,这些过程不能保证是原子执行的。

可见性:被volatile关键字修饰的变量,一旦在工作内存中被修改,则立刻同步回主内存,并且其他使用了这个变量的线程的工作内存会立刻从主内存读取新值。

禁止指令重排序:volatile变量在赋值后会创建一个内存屏障:指令重排序时,位于后面的指令不能排到内存屏障之前。

时间: 2024-11-03 05:42:15

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

Java多线程中的内存可见性

刚刚看了一下synchronized和volatile的区别,这里做一下笔记. 多线程中内存是如何分配的? 分为主内存和线程内存,当线程与其他线程共享一个变量时,便会把主内存的变量复制到线程内存中去.当发生对变量的修改时,会同步到主内存,主内存再同步到其他线程内存中去. Synchronized实现可见性 JMM对Synchronized规定: 线程加锁时,将清空线程内存中共享变量的值,从而使用共享变量时从主内存中重新读取新值. 线程解锁前,必须把共享变量的最新值刷新到主内存中. 线程执行互斥代

转载学习 多线程中的内存模型和关键字

类比与现代计算机的主存与cache,JVM中规定了 所有变量都存储在主内存中(类比计算机的主存),然后每条线程有自己的工作内存(类比每个处理器的cache).线程的工作内存中保存了该线程需要用到的变量的拷贝值,线程在CPU上运行时都是对自己工作线程中的数据进行读写操作,运行结束之后才把数据同步化主内存中.那么类比于计算机使用 缓存一致性协议 解决缓存一致性问题,JVM中就需要线程同步机制来达到多线程对同一内存区域的读写控制了. 此外,Java编译器为了提高性能,采取了 指令重排序(类比计算机的

java虚拟机:JVM内存模型

一.JVM内存模型图解 JVM 运行时数据区 (JVM Runtime Area) 其实就是指 JVM 在运行期间,其对JVM内存空间的划分和分配.网上找到两幅图如下所示(个人认为第二个图Native Method Stack应该画在Java Thead模块中):      二.各数据区域介绍 1.栈区 栈分为java虚拟机栈和本地方法栈 重点是Java虚拟机栈,它是线程私有的,生命周期与线程相同. 每个方法执行都会创建一个栈帧,用于存放局部变量表,操作栈,动态链接,方法出口等.每个方法从被调用

Java虚拟机学习 - 体系结构 内存模型(1)

一:Java技术体系模块图 二:JVM内存区域模型 1.方法区 也称"永久代" ."非堆",  它用于存储虚拟机加载的类信息.常量.静态变量.是各个线程共享的内存区域.默认最小值为16MB,最大值为64MB,可以通过-XX:PermSize 和 -XX:MaxPermSize 参数限制方法区的大小. 运行时常量池:是方法区的一部分,Class文件中除了有类的版本.字段.方法.接口等描述信息外,还有一项信息是常量池,用于存放编译器生成的各种符号引用,这部分内容将在类加

Java虚拟机学习 - 体系结构 内存模型

一:Java技术体系模块图 二:JVM内存区域模型 1.方法区 也称"永久代" ."非堆",  它用于存储虚拟机加载的类信息.常量.静态变量.是各个线程共享的内存区域.默认最小值为16MB,最大值为64MB,可以通过-XX:PermSize 和 -XX:MaxPermSize 参数限制方法区的大小. 运行时常量池:是方法区的一部分,Class文件中除了有类的版本.字段.方法.接口等描述信息外,还有一项信息是常量池,用于存放编译器生成的各种符号引用,这部分内容将在类加

Java虚拟机学习 - 体系结构 内存模型(转载)

一:Java技术体系模块图 二:JVM内存区域模型 1.方法区 也称"永久代” .“非堆”,  它用于存储虚拟机加载的类信息.常量.静态变量.是各个线程共享的内存区域.默认最小值为16MB,最大值为64MB,可以通过-XX:PermSize 和 -XX:MaxPermSize 参数限制方法区的大小. 运行时常量池:是方法区的一部分,Class文件中除了有类的版本.字段.方法.接口等描述信息外,还有一项信息是常量池,用于存放编译器生成的各种符号引用,这部分内容将在类加载后放到方法区的运行时常量池中

thread.join函数,java多线程中的join函数解析

join函数的作用,是让当前线程等待,直到调用join()的 线程结束或者等到一段时间,我们来看以下代码 1 package mian; 2 3 4 public class simpleplela { 5 static void threadMessage(String message) { 6 String threadName = 7 Thread.currentThread().getName(); 8 9 System.out.println(threadName+" "+m

java多线程中join用法

thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程.比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B. package com.wzs; /** * Java多线程中join用法 * * @author Administrator * */ public class JoinTest { public static void main(String[] args) { BThread bThread = new B

Java多线程中的死锁问题[转]

//申明:本文转载自http://www.cnblogs.com/digdeep/p/4448148.html <Java多线程中的死锁问题> Java程序基本都要涉及到多线程,而在多线程环境中不可避免的要遇到线程死锁的问题.Java不像数据库那么能够检测到死锁,然后进行处理,Java中的死锁问题,只能通过程序员自己写代码时避免引入死锁的可能性来解决. 1. Java中导致死锁的原因 Java中死锁最简单的情况是,一个线程T1持有锁L1并且申请获得锁L2,而另一个线程T2持有锁L2并且申请获得