Java内存 模型理解

概述

  在正式讲Java内存模型之前,我们先了解一些物理计算机并发问题,然后一点点的引出Java内存模型的由来。

  多任务处理在现在计算机操作系统中几乎是一项必备的功能。这不单是因为计算机计算能力强大,更重要的原因是计算机的计算速度远高于它的的存储和通信子系统速度。所以我们就通过让计算机同时处理多个任务来讲处理器的运算能力得到充分运用。

  除了充分运用计算机的处理能力外,一个服务端同时对多个客户端提供服务则是另一个更具体的并发应用的场景。衡量一个服务性能的高低好坏,每秒事务处理数(TPS)是一个重要指标,它代表着一秒内服务端平均能响应的请求总数,而TPS值和程序的并发能力又有非常密切的关系。对于计算量相同的任务,程勋线程的并发协调性越有条不紊,效率自然就会越高;反之,线程之间频繁的阻塞甚至死锁,将会大大降低程序的并发能力。

  在了解Java并发问题之前我们先了解一下,物理计算机的并发问题,物理机遇到的并发问题和虚拟机中的情况有喝多相似的地方,物理机对并发的处理方案对于虚拟机有很大的参考意义。

  前面说过,为了更充分的利用处理器的性能,我们让计算机并发执行多个运算任务,这种因果关系看起来顺理成章。但是他们的其实并没有这么简单,因为绝大多数的运算都不可能只靠处理器,处理器至少要和内存进行交互,如读物运算数据,存储运算结果等,这个I/O操作时很难消除的(无法紧靠寄存器来完成所有的运算任务)。所以现在计算机都会加入一层读写速度尽可能接近处理器运算速度的“高速缓存”,来作为处理器和内存之间的缓冲:将运算需要的数据复制到缓存中,让运算能快速的进行,当运算结束后从缓存同步回内存之中,这样处理器就不用等待缓慢的内存读写了。

  高速缓存很好的解决了处理器和内存的速度矛盾,但是这也为计算机系统带来了更高的复杂度,因为它引起了一个新的问题:缓存一致性。在多处理器系统中,每个处理器都有自己的高速缓存,二他们有共享一个主内存。当多个处理器的运算任务逗哦设计到同一块主内存区域时,将可能导致各自的缓存数据不一致。如果真的发生了缓存不一致的问题,那同步回到主内存时以谁的缓存数据为准呢?为了解决缓存一致性问题,需要各个处理器在访问缓存时都遵守一些协议,在读写时根据这些协议来进行操作。而在本文中要讨论的内存模型可以理解为在特定的操作协议下对特定的内存胡哦哦哦告诉缓存进行的读写访问的过程抽象。不同架构的物理机可以拥有不一样的内存模型,java虚拟机也有自己的内存模型。

  除了增加告诉缓存,为了使处理器内部的运算单元能尽量的被充分利用,处理器可能会对输入代码进行乱序执行优化,处理器会在计算之后将乱序执行的结果重组,保证该结果和顺序执行的结果是一致的。因此,如果哦存在一个计算任务以来另一个计算任务的中间结果,那么气顺序性并不能卡哦哦代码的先后顺序来保证。其实java虚拟机中指令重拍优化也是类似的优化。

为什么要定义Java内存模型

  java虚拟机规范中试图定义一种java内存模型(JMM)来屏蔽掉各种硬件和操作系统的内存访问差异,一实现让java程序在各种平台下都可能达到一致的内存访问效果。在此之前,C语音/C++直接使用物理硬件和操作系统的内存模型,所以就会出现在一套平台上并发访问正常,但是在另一套平台上却有问题,平台兼容性相对较差。

Java内存模型的目的及实现方式

   JMM的主要目标是定义程序中的各个变量的访问规则,即在虚拟机中将变量存储到内存和才哦哦欧诺个内存真够去除变量这样的底层细节。此处的变量与java变成中变量有所区别,她包括了实例字段,静态字段和构成数据对象的元素,但是不包括局部变量与方法参数,因为后者是线程私有的,不会被共享,自然就不会存在竞争问题。为了更好地性能,java内存模型并没有限制执行引引擎使用处理器的特定寄存器和珲春来和主内存进行交互,也没有哦限制即时编译器进行调整代码执行顺序这类优化。

  JMM规定所有的变量都存贮在主内存(虚拟机内存的一部分)中,每条现场还有自己的工作内存,线程的工作内存中保存了该线程使用到的主内存中的变量的副本(注意:如果一个对像如果10M,是不是会把这个10M的内存复制一份到工作内存呢?显然是不会的,但是这个对像的引用,对像中的某个在线程中访问到哦的字段是有可能会复制到工作能存中的,但是不会把整个对象复制一份),线程对变量的所有操作(读取,赋值等)都需要在工作内存中进行,二不能直接读写主内存中的变量。不同线程之间也是无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。

  另外要注意,这里所说的主内存,工作内存和java内存区域中的java堆,栈,方法区等并不是一个层次的内存划分,这两者没有任何关系,如果非要勉强对应的话,主内存主要对应于java堆中的对象实例数据部分,而工作内存则对应于虚拟机栈中的部分区域。从更低层次上说,主内存就是直接对应于物理硬件的内存,而为了获取更好额的运行速度,虚拟机可能会让工作内存有限存储于寄存器和高速缓存中,因为程序运行时主要访问读写的是工作内存。

主内存和工作内存之间的交互

  Java内存模型定义了8种操作来完成关于主内存和工作内存之间具体的交互,这些操作都是原子的,不可分割(long double类型除外)。这8种操作如下所示:

  • 1) lock(锁定) 作用于主内存的变量,它把一个变量标志为一条线程独占的状态
  • 2) unlock(解锁) 作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其它线程锁定
  • 3) read(读取) 作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
  • 4) load(载入) 作用于工作内存的变量,它把read操作从主内存得到的变量值放入工作内存的变量副本中
  • 5) use(使用) 作用于工作内存的变量,它把变量副本的值传递给执行引擎,每当虚拟机遇到一个需要使用的变量的值的字节码指令时,将会执行这个操作。
  • 6) assign(赋值) 作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作副本变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作
  • 7) store(存储) 作用于工作内存的变量,将工作副本变量的值传输给主内存,以便随后的write操作使用
  • 8) write(写入) 作用于主内存的变量, 它把store操作从工作内存得到的变量的值放入主内存的变量

  如果要把一个变量从主内存复制到工作内存,那就要按顺序地执行read和load操作,如果要把变量从工作内存同步回主内存,那就要顺序地执行store和write操作。注意,Java内存模型只要求上述两个操作必须按顺序地执行,而没有保证必须是连续执行,也就是说read和load之间,store和write之间是可以插入其它指令的,如对内存中的变量a,b进行访问时,一种可能出现的顺序是read a, read b, load b, load a。

  除此之外,java内存模型还规定了在执行上述8中基本操作时必须满足的规则。这里不在详述。

  通过这8中内存访问操作及其相关的规定,再加上volatile的一些特殊规定,就完全可以确定哪些内存访问操作在并发下是安全的。由于这种定义相当严谨但又十分的繁琐,实践起来很是麻烦,所以java虚拟机提供了一个等效判断原则--先行发现原则

volatile的含义和用法

  • volatile的语义

  第一:保证了次变量对所有的线程是可见的,这里的可见性是指当一个线程修改了这个变量的值,新值对于其他线程来说是可以立即得知的。

并发安全过程中三原则

原子性

可见性

一致性

猜你喜欢

转载自www.cnblogs.com/htyj/p/10853384.html