java内存模型
java线程 -> 工作内存 -> 主内存
每个线程有自己的工作内存,线程的工作内存中保存了被该线程使用的变量的主内存副本,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的数据。
java内存模型定义了以下8种用于主内存和工作内存之间的同步的操作:
lock、unlock、read(读取)、load(载入)、use(使用)、assign(赋值)、store(存储)、write(写入)
如果把一个变量从主内存拷贝到工作内存,那就要顺序执行read和load操作,如果要把变量从工作内存同步会主内存,就要按顺序执行store和write。注意,java内存模型只要求上述两个操作顺序执行,但是不要求连续孩子能够,之间可以插入其他指令。
这8种操作比较繁琐,后来java设计团队将其简化为read、write、lock和unlock这4个操作。
volatile
volatile修饰的变量具有以下两个特性:
1、保证此变量对所有线程的可见性。一个线程修改了这个变量的值,新值对于其他线程来说是可以立即得知的。
2、禁止指令重排序优化。加入内存屏障,指令重排不能将后面的指令排到内存屏障之前的位置。
java内存模型对volatile变量的特殊规定:
1、只有当线程T对变量V执行的前一个动作是load的时候,线程T才能对变量V执行use动作。可以认为线程T对变量的use动作可以认为和线程T对变量的load、read动作相关联,必须连续且一起出现。
这条规则要求在工作内存中,每次使用V前必须先从主内存刷新最新的值,用于保证能看到其他线程对V所做的修改。
2、只有当线程T对变量V执行的前一个动作是assign的时候,线程T才能对变量V执行store动作。assigon、store和write是连续且一起出现。
这条规则要求在工作内存中,每次修改V后都必须立刻同步会主内存,保证其他线程可以看到自己对于变量V所做的修改。
对long和double类型的特殊规则:对于64位的数据类型(long和double),允许虚拟机将没有被volatile修饰的64位数据的读写划分为两次32位操作来进行,即不保证原子性。
volatile可以保证可见性,但不保证原子性。
先行发生
先行发生原则(Happens-Before)是判断数据是否存在竞争,线程是否安全的非常有效的手段。如果两个操作有着明确的先行发生关系,那么就不存在竞争。
如果操作A先行发生于操作B,其实就是说操作A产生的影响能被操作B观察到,“影响”包括修改内存中的共享变量的值,发送了的消息,调用的方法等。
“时间上的先后关系”和“先行发生”是不一样的。
java内存模型下“天然的”先行发生关系:
- 程序次序规则:这里说的是流程控制顺序而不是程序代码顺序。
- 管程锁定规则:同一个锁时间先后上的lock和unlock
- volatile变量规则:同一个volatile变量时间先后上的操作
- 线程的启动规则:thread的start方法先行于此线程的每一个动作
- 线程的终止规则
- 线程的终端规则
- 对象的终端规则:一个对象的初始化完成先行发生于它的finalize方法的开始
- 传递性:A先行于B,B先行于C,则A先行于C