谈谈Java内存模型
什么是JMM
Java Memory Model ,Java内存模型,是一种共享内存系统中多线程程序读写操作行为的规范,它是一种屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制及规范。说到底,他是一种规范,而不是物理上存在的东西。
为什么需要这种规范呢?
这就牵扯到硬件上的知识。这里做简单介绍。我们知道,Java中每一条指令都需要在CPU中去执行,当然免不了和数据打交道,而数据是存储在主内存中的,这就出现了一个问题,就是CPU的处理速度太快,而主内存的数据读取和写入的数据太慢,这就导致CPU每次操作都需要消耗很多时间。由此,CPU高速缓存出现,现在的电脑都是多核的,也就是每个CPU中都有高速缓存,如下图所示,就是三级缓存。(CPU缓存很贵,所以容量很小)
这样,当CPU要读取一个数据时,首先从一级缓存中查找,如果没有找到再从二级缓存中查找,如果还是没有就从三级缓存或内存中查找。
一个CPU会从主内存中拷贝一份共享变量到自己的工作内存中去,利用工作内存中的数据,进行操作。但这个会引发一个问题,就是数据一致性问题,在多线程的情况下,线程A对共享变量操作之后,共享变量的值已经发生了改变,还没存储到主内存中去,而线程B却还是使用的之前从主内存中拷贝的数据,此时,数据就会不一致。
除了数据不一致问题,还有一个重要问题就是指令重排问题。为了使处理器内部的运算单元能够尽量的被充分利用,处理器可能会对输入代码进行乱序执行处理。这就是处理器优化。很多编程语言的编译器也会有类似的优化,比如Java虚拟机的即时编译器(JIT)也会做指令重排。
综上,为了解决这些问题,需要一种规范来防止这些问题的发生。
三大特性
内存模型需要满足这三大特性:
原子性是指在一个操作中就是cpu不可以在中途暂停然后再调度,既不被中断操作,要不执行完成,要不就不执行。
可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
有序性即程序执行的顺序按照代码的先后顺序执行。
在Java中是怎么满足的呢?
原子性
在Java中,为了保证原子性,提供了两个高级的字节码指令monitorenter和monitorexit。这两个字节码,在Java中对应的关键字就是synchronized。
因此,在Java中可以使用synchronized来保证方法和代码块内的操作是原子性的。
可见性
Java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值的这种依赖主内存作为传递媒介的方式来实现的。
Java中的volatile关键字提供了一个功能,那就是被其修饰的变量在被修改后可以立即同步到主内存,被其修饰的变量在每次是用之前都从主内存刷新。因此,可以使用volatile来保证多线程操作时变量的可见性。
除了volatile,Java中的synchronized和final两个关键字也可以实现可见性。
有序性
在Java中,可以使用synchronized和volatile来保证多线程之间操作的有序性。实现方式有所区别:
volatile关键字会禁止指令重排。synchronized关键字保证同一时刻只允许一条线程操作。