JVM基础(二):Java内存模型

前置知识

1.多线程问题

2.主内存与工作内存

1.多线程问题

1.1为什么要使用多线程?

  现在计算机的运算速度与它的存储和通信速度的差距太大,大量的时间都花费在磁盘I/O、网络通信和数据库访问上。如果不希望处理器在大部分时间里都处于等待其他资源的状态,就必须使用一些手段去把处理器的运算能力“压榨”出来,否则就会造成很大的浪费,而多线程就是很好的解决方法。

1.2内存之间如何通信?

什么是通信:通信是指线程之间以何种机制交换信息

在命令式编程中,线程之间的通信机制有两种:

  1. 共享内存
  2. 消息传递

1.共享内存的并发模型里,线程之间共享程序的公共状态,通过写-读内存中的公共状态进行隐式通信。

2.消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过发送消息来显式进行通信。

Java的并发采用的是共享内存模型。

1.3多线程有什么问题?

  由于计算机的存储设备与处理器的运算速度有几个数量级的差距,所以现代计算机都不得不加入一层读写速度尽可能接近处理器运速度的【高速缓存(Cache)】来作为内存与处理器之间的缓冲:将运算需要使用到的数据赋值到缓存中,让运算能快速进行,当运算结束后在从缓存同步会内存中,这样处理器就无须等待缓慢的内存读写了。


  基于高速缓存的存储交互很好的解决了处理器与内存的速度矛盾,但是也为计算机系统带来更高的复杂度,因为它引入了一个新的问题:缓存一致性(Cache Coherence)。在多处理器系统中,每个处理器都有自己的高速缓存,而他们又共享一个主内存(Main Memory),如图所示。当多个处理器的运算任务都涉及同一块主内存区域时,将可能导致各自的缓存数据不一致,如果真的发生这种情况,那同步回到主内存时以谁的缓存数据为准呢?为了解决一致性的问题,需要各个处理器访问缓存时都遵循一些协议,在读写时要根据协议来进行操作。而“内存模型”的出现就是为了解决这样问题,“内存模型”可以理解为在特定的操作协议下,对特定的内存或高速缓存进行读写访问的过程抽象。不同架构的物理机器可以拥有不一样的内存模型,而Java虚拟机也有自己的内存模型,这就是接下来要讲的【Java内存模型】

2.主内存与工作内存

Java内存模型规定了所有的变量都存储在【主内存】中,而每个线程都有自己的【工作内存】。

每个线程的【工作内存】中保存了被该线程使用到的变量的【主内存副本拷贝】,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量。

扫描二维码关注公众号,回复: 647456 查看本文章

不同的线程之间也无法直接访问对方的【工作内存】中的变量,线程间变量值的传递均需要通过主内存来完成,线程、主内存、工作内存三者的交互关系如图。


---------------------------------------------------------------------------------------------------

Java内存模型(JMM)

概述:

Java内存模型(即Java MemoryModel,简称JMM)本身是一种抽象的概念,并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。在java中,所有实例域、静态域和数组元素存储在堆内存中,堆内存在线程之间共享(本文使用“共享变量”这个术语代指实例域,静态域和数组元素)。局部变量(Local variables),方法定义参数(java语言规范称之为formal method parameters)和异常处理器参数(exceptionhandler parameters)不会在线程之间共享,它们不会有内存可见性问题,也不受内存模型的影响。

Java线程之间的通信由Java内存模型控制:线程之间的共享变量存储在【主内存】中,每个线程都有一个私有的【本地内存】,本地内存中存储了该线程以读/写共享变量的副本


如图,线程A与线程B之间如果要通信的话,必须要经历下面2个步骤:

1)线程A把【本地内存A】中更新过的【共享变量】刷新到主内存中去。

2)线程B到【主内存】中去读取被线程A【更新过的共享变量】。

原子性、可见性与有序性

Java内存模型是围绕着并发过程中如何处理原子性可见性有序性这3个特征来建立的。

  1. 原子性:由Java内存模型来直接保证的原子性变量操作包括read、load、assign、use、store和write。
  2. 可见性:可见性是指当一个线程修改了共享变量的值,其他线程能够立刻得知这个修改。
  3. 有序性:Java中天然的有序性可以总结为一句话:如果在本线程内观察,所有的操作都是有序的;如果在一个线程中观察另一个线程,所有的操作都是无序的。前半句是指”线程内表现为串行的语义“,后半句是指”指令重排序“现象和”工作内存与主内存同步延迟“现象。

重排序

什么是重排序:

重排序是指①编译器和②处理器为了优化程序性能而对指令序列进行重新排序的一种手段。

在执行程序时为了提高性能,编译器和处理器常常会对指令做重排序。重排序分三种类型:

  1. 编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
  2. 指令级并行的重排序。现代处理器采用了指令级并行技术(Instruction-Level Parallelism, ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
  3. 内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。

上述的1属于编译器重排序,2和3属于处理器重排序。这些重排序都可能会导致多线程程序出现内存可见性问题。对于编译器,JMM的编译器重排序规则会禁止特定类型的编译器重排序(不是所有的编译器重排序都要禁止)。对于处理器重排序,JMM的处理器重排序规则会要求java编译器在生成指令序列时,插入特定类型的内存屏障(memory barriers,intel称之为memory fence)指令,通过内存屏障指令来禁止特定类型的处理器重排序(不是所有的处理器重排序都要禁止)。

JMM在不同的编译器和不同的处理器平台之上,通过禁止特定类型的编译器重排序和处理器重排序,为程序员提供一致的内存可见性保证。

Happens-before 

在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关系。这里提到的两个操作既可以是在一个线程之内,也可以是在不同的线程之间。

如果线程 A 与线程 B 满足happens-before 关系,则线程 A 执行动作的结果对于线程 B 是可见的。如果两个操作未按 happens-before 排序,JVM 将可以对他们任意重排序。

  1. 程序次序法则:如果在程序中,所有动作 A 出现在动作 B 之前,则线程中的每动作 A 都 happens-before 于该线程中的每一个动作 B。
  2. 监视器锁法则:对一个监视器的解锁 happens-before 于每个后续对同一监视器的加锁。
  3. Volatile 变量法则:对 Volatile 域的写入操作 happens-before 于每个后续对同一 Volatile 的读操作。
  4. 传递性:如果 A happens-before 于 B,且 B happens-before C,则 A happens-before C。

volatile

猜你喜欢

转载自blog.csdn.net/crankz/article/details/80167515