####Java内存模型
Java内存模型描述了Java虚拟机和计算机内存之间是如何协同工作的。一个Java虚拟机也是一个完整的计算机的模型,因此,这个模型自然也包含了内存模型。
如果你想写出表现良好的并发程序就必须理解Java内存模型。Java内存模型描述了不同线程间如何和何时看到被其他线程修改的共享变量以及在需要时如何同步访问共享变量。
原来的Java内存模型存在很多不足,所以在Java5时进行了修改。这个一直使用至今。
####Java内存模型
每个运行在Java虚拟机中的线程都拥有自己的线程栈。这个线程栈包含了这个线程所调用方法的当前执行点,所有我们也可以称之为”调用栈“。在线程执行代码的过程总,调用栈随之发生变化。
线程栈也包含每个被执行的方法中的所有局部变量。一个线程只能访问自己的线程栈。一个线程创建的局部变量对其他线程是不可见的。即使两个线程执行同样的代码,这两个线程任然需要在它们各自的线程栈中创建这些变量。
所有的基本类型的局部变量都全部存放在各自的线程栈中,对其他线程不可见。一个线程可能会向另一个线程传递一个基本类型变量的拷贝,但是这并不能共享基本类型变量自身。
在堆中包含所有你在Java程序中创建的对象。这也包含所有基本类型所对应的装箱类型。即便,我们创建了一个对象然后我们把它赋给了一个局部变量,或者作为另一个对象的成员变量,这个对象仍然存放在堆中。
一个局部变量可能是基本类型,这样它就永远呆在线程栈中。
一个局部变量也可能是引用变量。在这种情况下,引用变量存放在线程栈中,对象本身存放在堆中。
一个对象可能包含方法,这些方法又可能包含局部变量。这些局部变量也被存放在线程栈中,即便这个方法所属的对象存放在堆中。
一个对象的成员变量随着对象自身存放在堆中。不管这个变量是基本类型还是引用类型都是如此。
静态类变量随着类定义也存放在堆中。
存放在堆中的对象可以被所有的线程通过指向对象的引用访问。当一个线程访问一个对象时,它也可以访问这个对象的成员遍历。如果两个线程在同一时刻调用同一个对象上的同一个方法,它们都可以访问对象的成员变量,但是每个线程都会拥有各自的局部变量拷贝。
####硬件内存架构
现代硬件内存架构和Java内存模型有一些不一样的地方。理解硬件内存架构和Java内存模型如何和它协同工作也非常重要。
通用的硬件内存架构:
现代计算机通常拥有两个或多个CPU。这些CPU中可能还有是多核的。这一点,使得多个线程同时运行在一台计算机上称为了可能。每个CPU可以在任何时刻运行一个线程。
这就意味着如果你的程序是多线程的,在你的程序内部,一个线程对应一个CPU可能同时运行。
每个CPU包含一些寄存器。CPU可以在这些寄存器上执行操作会比在内存上快很多。这是因为CPU访问寄存器的速度远高于访问内存的速度。
每个CPU还可能拥有一个CPU缓存层。实际上,现代计算机都会有一个一定大小的缓存层。CPU访问缓存的速度远高于主存,但通常又低于访问寄存器的速度。因此,缓存是用来平衡CPU访问寄存器和主存之间的速度差异的。一些CPU可能拥有多级缓存(一级缓存和二级缓存)。
一台计算机还拥有一块主存区域(RAM)。所有的CPU都可以访问主存。主存区域通常要比CPU的缓存大得多。
通常,当一个CPU需要访问主存的时候,它会将数据从主存读到CPU的缓存中,甚至再从CPU的缓存读到它内部的寄存器中,然后执行相关的操作。当CPU需要将结果写回到主存中时,它会先将值刷新到缓存中,然后在某一时刻刷新回主存中。
当CPU需要在缓存中存储一些其他的东西时,会将存储在缓存中的值刷新回主存中。
CPU缓存可以局部刷新。
####Java内存模型和硬件内存架构之间的联接
正如上面所提到的,Java内存模型和硬件内存架构是不同。在硬件内存架构并不区分内存栈和堆。在硬件中,所有的线程栈和堆都位于主存中。线程栈的一部分和堆可能同一位于CPU缓存和寄存器中。
当对象和变量存放在计算机的不同内存区域中时,就会暴露出一些问题。主要包括两个方面:
- 内存可见性
- 当读,检查和写共享变量时的竞争条件
#####内存可见性
如果两个或多个线程共享同一个对象时,在不使用vloatile
声明或者同步的情况下,一个线程更新了这个共享对象的值可能对其他线程不可见。
想象一下,这个共享对象最初存放在主存中。运行在一个CPU上的一个线程,将这个共享对象读到它的CPU缓存中。并在缓存中修改了这个共享对象。只要这个CPU缓存还没有刷新回主存,这个共享共享对象变化后的版本对其它CPU的线程来说就是不可见的。这种方式可能使每个线程最终拥有这个共享对象的拷贝,每个拷贝都停留在不同的CPU缓存中。
解决这个问题,可以使用Java中的volatile
关键字。volatile
关键字可以确保你直接从主存中读取一个给定的变量,当变量发生更新总是会被写回到主存中。
#####竞争条件
如果两个或者多个线程共享一个对象,超过一个对象去更新对象上的变量,竞争条件可能就会发生。