java内存模型
java虚拟机规范试图定义一种java内存模型来屏蔽掉各种硬件和操作系统的内存访问差异,实现java程序在各平台下都能达到一致的并发效果。c与c++直接使用物理硬件(操作系统的内存模型),因此需要针对不同平台编写程序。
主内存和工作内存
java内存模型的主要目标是定义程序中各变量的访问规则,即虚拟机中将变量存储到内存和从内存中取出变量的底层细节。这里的变量包括实例字段,静态字段,和构成数组对象的元素,因为他们在多线程下,会存在竞争,不包括线程私有的局部变量和方法参数。
java内存模型规定 所有变量都储存在 主内存,除此之外,每个线程还有自己的工作内存。线程的工作内存中保存了该线程使用到的变量的主内存副本拷贝,线程对于变量的所有操作都在工作内存,不能直接读写主内存。线程直接也不可以访问各自的工作内存的变量,变量值的传递通过主内存完成。
这里讲的内存模型和java内存区域中的堆栈不是一个层次的内存划分。
内存间的交互
主内存和工作内存的交互,通过下面八种方式来完成。
- lock(锁定):作用于主内存变量,把变量标识为线程独占状态。
- unlock(解锁):作用于主内存变量,把锁定的变量释放
- read(读取):作用于主内存变量,把变量的值(只是值,不是变量)从主内存传输到线程的工作内存,以便load操作使用
- load(载入):作用于工作内存变量,把read操作得到的变量值放入工作内存的变量副本中。
- use(使用):作用于工作内存变量,把变量的值传递给执行引擎。
- assign(赋值):作用于工作内存变量,把从执行引擎收到的值赋值给工作内存的变量。
- store(存储):作用于工作内存。把工作内存中的变量传送给主内存,以便随后write使用
- write(写入):作用于主内存变量,把store操作从工作内存得到的变量值放到主内存变量中。
上述八种操作必须满足一些规则,这里就不列举了,感兴趣自行查阅。
volatitle关键字
volatitle关键字是java虚拟机提供的最轻量级的同步机制。它的性能和普通变量相比,读的速度几乎一样,但写的速度会慢一些,因为需要在本地代码中插入一些内存屏障。
java内存模型对volatitle专门定义了一些特殊的访问规则:可见性和禁止指令重排
可见性
当一个变量值被修改,其他线程可以立即得知,仅仅是得知(通过每次使用前都从主内存刷新最新值,每次赋值后,都写入主内存实现)。但是,这不代表线程安全。只能保证可见,就是我们的线程的知道它改变了,但我们对变量执行其他的操作的时候,这个值可能已经被其他线程修改了。类似 i++的整个过程分为步骤1: 看到值,步骤2:修改值,通过volatitle关键字,我们可以保证步骤1总是正确,但是步骤2不一定正确,因为这个过程是非原子的。该关键字不能保证整个过程为原子操作,如果需要整个过程是原子操作,就使用sycchozied。
所以使用volatitle关键字需要满足两个规则:
(1)运算结果不依赖当前值,或者保证只有单一线程修改变量值。
(2)变量不需要与其他状态变量共同参与不变约束。
禁止指令重排
java内存模型中,线程内表现为串行语义,这就是说,线程内,赋值都能得到正确结果,语义串行,但代码执行顺序不一定保证,可能被优化。这在单一线程中无关紧要,但是在多线程中,就可能造成不良影响。例如,A线程 我们代码顺序是 先实例化一个对象,然后打开开关。在B线程中检测到开关打开,然后引用A线程中实例化的对象做一些事情。如果进行了指令重排,可能我们B线程就会引用到一个空对象。
long和double型变量的特殊规则
java内存模型对lock等八大操作都要求具有原子性,但是对于64位的数据类型(long和double),允许虚拟机将没有被volatitle修饰的64位数据,读写划分为两次32位操作,即 read ,load,assgin,write可以不保证原子性,这就是非原子协定。这在多线程过程中,就可能存在读取到半个变量的情况,就是第一次32位操作后就被其他线程读取,这种情况是很罕见的。现在商业虚拟机几乎都选择把64位数据的读写操作当做原子操作看待,所以对于double和long,没有必要加volatitle修饰。
原子性,可见性,有序性
java内存模型的三个特征,就是原子性,可见性,有序性。
- 原子性:java内存模型保证原子性变量操作(包括read等六个操作),我们大致可以认为基本数据类型的访问读写是原子性的(非原子协定暂时不考虑),更大访问的原则性保证由lock和unlock提供,在java代码的体现就是synchronized块。
- 可见性:当一个线程修改了共享变量的值,其他线程能够立即知道。java内存模型通过变量修改后同步回主内存,变量读取前从主内存刷新变量值的方式实现可见性。普通变量和volatitle变量都是如此,但是volatitle变量特殊的机制,保证新值能立即同步,每次使用前立即从主内存刷新。
- 有序性:本线程内观察,所有操作都是有序的(线程内串行语义);在一个线程观察另外一个线程,所有都是无序的(指令重排现象和工作内存与主内存同步延迟现象)。
java语言提供volatile关键字和synchonized关键字保证线程之间操作的有序性,volatile本身禁止指令重排,synchonized通过“在同一时刻,只允许一条线程对其进行lock操作”获得,这就保证了两个同步块,只能串行的进入。
先行先发生原则
时间上的先发生和先行先发生并不相互影响。即时一个操作时间上,被处理器先处理,但不影响它在时间上后处理的操作之后执行,所以二者不能相互推导,这就是指令重排。
一些天然的先行发生关系:
- 程序次序规则
- 管程锁定规则
- volatile规则
- 线程启动规则
- 线程终止规则
- 线程中断规则
- 对象终结规则
- 传递性
具体内容可以自行查阅。