一、Java内存模型概念
Java内存模型(Java Memory Model,简称JMM)是一种规范,它规范了java虚拟机与计算机内存如何协同工作,规定了一个线程如何和何时可以看到其他线程修改过的共享变量的值,以及在必须时如何同步的访问共享变量。
二、JVM内存分配:堆和栈
堆(heap):是运行时的数据区,由垃圾回收负责。优势是可以动态的分配内存大小,生存期也不必事先告诉编译器,缺点是存取速度慢(因为要动态分配内存)。
栈(stack):存取速度比堆快,仅次于计算机中的寄存器,栈的数据可以共享,缺点是存在栈中的数据的大小和生存期必须是确定的,缺乏灵活性,栈中存放一些基本类型的变量。
一个本地变量,可能指向一个对象的引用,引用这个本地变量是存放在线程栈上,但是对象本身是存放在堆上。
一个对象可能包含方法, 可能包含本地变量,这些本地变量仍然是存放在线程栈上的,即使这些方法所属的对象存放在堆上。
一个对象的成员变量会随着这个对象的自身存放在堆上,不管这个对象是原始类型还是引用类型。
静态成员变量跟随着类的定义一起存放在堆上。存放在堆上的对象可以被持有对这个对象的引用线程访问。
当一个线程可以访问一个对象的时候,这个线程也可以访问它的成员变量,如果两个线程同时调用同一个对象的同一个方法,它们将会都访问这个对象的成员变量。每一个线程都拥有了这个对象的私有拷贝。
三、计算机硬件架构
每个CPU都包含一系列的寄存器(Register),CPU在寄存器上执行的速度远大于在主存上执行的速度。
高速缓存:由于计算机存储设备与CPU的计算速度之间差距很大。计算机会将运算需要使用的数据复制到缓存中,让运算可以快速的运行。当运算结束后,再从缓存同步回内存中。这样CPU就不用等待内存的缓慢的读写了。
运行原理:当一个CPU需要读取一个主存的时候,会将主存的部分读取到CPU缓存中,甚至会将缓存的部分内容读到寄存器中,然后在寄存器中执行操作。当CPU需要将结果回写到主存的时候,会将寄存器的值刷新到缓存中,然年在某个时间点刷新回主存。
四、JMM和计算机硬件之间关系
线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存。本地内存是JAVA内存模型的抽象概念,并不是真实存在的,它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器的优化。本地内存中存储了该线程读写共享变量的拷贝的一个副本。
比如线程A要使用主内存的一个变量,则先拷贝出来这个变量的副本,放在自己的本地内存里。
java内存模型中的线程的工作内存是CPU的寄存器和高速缓存的一个抽象的描述。而JVM静态内存存储模型只是对内存的物理划分而已。只局限在JVM内存,如果线程间通信,必选要经过主内存。
线程A把本地内存A中更新过的共享变量刷新到主内存中,线程B到主内存中去读取线程A之前已经更新过的共享变量。
假设主内存中当前变量的值为1,线程A和线程B同时开始执行,线程A从主内存中拿到的变量值是1,存到自己的本地内存A中,然后执行+1的操作,线程A计算完之后得到的结果为2,然后将它写回到主内存,变量变为2。
而线程A从主内存中拿到值为1的同时,线程B从主内存中拿到的值也是1,存在线程B的本地内存B中,执行+1操作变成2,在线程A将2写回到主内存的同时,线程B也开始将自己计算后的结果2写回到主内存。计算过程中两个线程的数据是互相不可见的,因此计数出现了错误,所以我们要增加一些同步的手段。
五、JMM之同步八种操作
过程如下:
Lock:作用于主内存的变量,把一个变量标识为一个线程独断的状态。
Unlock:作用于主内存,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
Read:作用于主内存变量,把一个变量从主内存输送到工作内存中,以便随后的Load动作才可以使用。
Load:作用于工作内存的变量,把通过read操作得到的变量值放入到工作副本中。
Use:作用于工作内存变量,把工作内存中的一个变量传送给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时,就会执行这个操作。
Assign:作用于工作内存的变量,把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时,会执行这个操作。
Store:作用于工作内存的变量,把工作内存总的一个变量值传递到主内存中,以便随后的write操作。
Write:作用于主内存变量,把store从工作内存中得到的变量值放入到主内存的变量里。
对于+1操作,不涉及lock和unlock,因此读到1,载入到工作内存里,java线程使用它,计算为2,2之后,指定工作内存变为2,然后写回到主内存中。