一、硬件的效率与一致性
由于计算机的存储设备与处理器的运算速度有几个数量级的差距,所以现代计算机系统都不得不加入一层读写速度尽可能接近处理器运算速度的高速缓存(Cache)来作为内存与处理器之前的缓冲:将运算需要用到的数据复制到缓存中,让运算能快速运行,当运算结束再从缓存同步回内存之中,这样处理器就无需等待缓慢的内存读写了。
基于高速缓存的存储交互很好的解决了处理器与内存的速度矛盾,但是也引入了一个新的问题:缓存一致性。当多个处理器的运算任务都涉及到同一块主内存区域时,将可能导致各自的缓存数据不一致,那同步回到主内存时以谁的缓存数据为准呢?为了解决一致性的问题,需要各处理器访问缓存时都遵循一些协议,在读写时要根据协议来操作,这里说的协议就是内存模型的抽象。Java虚拟机也有自己的内存模型。
二、Java内存模型
在JDK1.5发布后,Java虚拟机规范中定义的Java内存模型(Java Memory Model JMM)已经成熟和完善。它屏蔽掉各种硬件和操作系统内存的访问差异,以实现让Java程序在各种平台下都能达到一致的访问效果。
三、主内存和工作内存
Java内存模型规定所有变量都存储在主内存(Main Memory)中(可以理解为物理内存,不过是虚拟机内存的一部分),每条线程还有自己的工作内存(Woring Memory,可以理解为前面讲的高速缓存),线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作(读取,赋值)都必须在工作内存中完成。
如多线程执行变量i++操作的流程:
1).先将变量i读取到工作内存中;
2).然后在工作内存中将i+1;
3).最后将变量i同步到主内存中。
四、原子性、可见性与有序性
Java内存模型的三大特征:原子性、可见性与有序性
1.原子性(Atomicity):即一个操作要么全部执行并且执行的过程中不被任何因素打断,要不都不执行。
如多线程执行i++操作
public class Test implements Runnable{
int i = 0;
public void run(){
i++;
}
}
public class Test implements Runnable{ int i = 0; public void run(){ i++; } } 假如i初始值为0,线程1和线程各自执行一次+1操作,结果是我们想要的2吗?不一定根据前面讲的内存模型,假如线程1将i=0读取的工作内存中,并对i+1,此时i=1,但只是在线程1的工作内存中,并未同步到主存中。
此时线程2从主存读取i还是=0,并对i+1变为1,此时i=1,但只是在线程1的工作内存中,并未同步到主存中。
然后线程1同步到主存中,最后线程2同步到主存中,程序执行完毕,i的值为1。
这种情况就是原子性操作被打断了。哪如何保证原子性不被打断呢,Java提供了两种方式Lock和synchronized,后续再讲。
2.可见性(Visibility)是指当一个线程修改了共享变量的值,其它线程能够立即得知这个修改。
Java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值来实现可见性,无论是普通变量还是volatile变量都是如此,只不过volatile特殊规则保证了新值能够立即同步到主内存,以及每次使用前都从主内存刷新。因此,可以说volatitle保证了变量的可见性,而普通变量不可以。
除了volatitle之外,java还有两个关键字保证可见性,即synchronized和final。
同步的可见性是由“对一个变量执行unlock操作之前,必须先把此变量同步回主内存中”这条规则获得。
而final关键字的可见性是指,被 final修饰的字段一旦在构造器中初始化完成,并且构造器没有把“this”引用传递出去,那在其它线程中就能看见final字段的值。
public static final int i;
public final int j;
static{
i = 0;
}
{
//也可以在构造器初始化
j = 0;