Java 内存模型
JVM系统中存在一个主内存(Main Memory),Java中所有变量都储存在主存中,对于所有线程都是共享的。每条线程都有自己的工作内存(Working Memory),工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作都是在工作内存中进行,线程之间无法相互直接访问,变量传递均需要通过主存完成。
模型的规则:
1.原子性:保证程序得到成员变量(非局部变量)的值或者是初始值,又或者是某线程修改后的,绝对不是多个线程混乱修改后的。
2.可见性(共享内存的数据):什么情况下,写入成员变量的值对读取该变量的值是可见的?
A.写操作释放了同步锁,读操作获得了同步锁
原理:释放锁的时候强制线程把所使用的工作内存中的值刷新到主存,获得锁的时候从主存重新装载值。
p.s.锁只被同步块和方法中的操作占有,但却控制了执行该操作的线程的所有成员变量。
B.如果一个成员变量为volatile,那么在写线程做存储操作前,写入这个成员变量的数据会在主存中刷新,并对其他线程可见。读线程每次使用这个成员变量前都要重新从主存读数据。
C.如果一个线程访问一个对象的成员变量,读到的值为初始值或者另一个线程修改后的值。
p.s. 不要对引用未完全创建好的对象。
如果一个类可以被子类化,那么在构造函数里启动一个线程是非常危险的
D.当一个线程结束后,所有的写入数据都会被刷新到主存。
p.s.同一个线程的不同方法之间传递对象的引用,永远不会有可见性问题
存储模型保证:如果上面的操作都会发生,那么一个线程对一个成员变量的更新最终对另一个线程是可见的。
3.顺序化(内存操作的顺序):什么情况下,一个线程的操作可以是无序的?顺序化的问题主要围绕和读写有关的赋值语句的执行顺序。
如果采用同步机制,那不用多说,顺序化可以保证。
当没有同步机制时,存储模型所做的保证是难以相信的。在多线程环境下,存储模型是难以保证一定正确的。
只有当满足下面的三个原则,顺序化才得以保证。
A.从线程执行方法的角度看,如果指令都是串行执行的,那么顺序可以保证
B.保证同步方法或块的顺序执行
C.使用volatile定义成员变量
线程执行过程中,存储模型与锁的关系:
(1) 获取对象的锁
(2) 清空工作内存数据, 从主存复制变量到当前工作内存, 即同步数据
(3) 执行代码,改变共享变量值
(4) 将工作内存数据刷回主存
(5) 释放对象的锁
最后介绍一下volatile关键字
volatile定义的成员变量可以保证可见性和顺序化,但不保证原子性。比如count++。
*比如把一个变量声明为volatile,并不能保证这个变量引用的非volatile数据的可见性。比如volatile string[10](数组)
正确使用volatile的前提条件
a.对变量的写操作不依赖于当前值
b.不要和其他成员变量遵守不变约束。见*处的解释
volatile的应用
a.状态标志
volatile boolean shutdownFlag;
public void shutdown() { shutdownFlag= true; }
public void doWork() {
while (!shutdownFlag) {
// do something
}
b.假设一个后台线程可能会每隔几秒读取一次数据库里的合同金额,并更新至 volatile 变量。然后,其他线程可以读取这个变量,从而随时能够看到最新的金额。 比较广泛应用在统计类的系统中。
参考文档:
http://www.cs.umd.edu/~pugh/java/memoryModel/
http://www.ibm.com/developerworks/cn/java/j-jtp06197.html
《Java并发编程:设计原则与模式》