概述
由于计算机计算能力与它的存储和通信子系统的速度差距过大,所以必须使用一些手段来压榨处理器的运算能力,这个手段之一就是多线程并发。
硬件的效率与一致性
由于计算速度与IO操作的速度差异了几个数量级,所以不得不为现代计算机系统加入一层或者多层读写速度尽可能接近处理器运算速度的高速缓存。
但是由于处理器都共享同一块主存但是彼此之间的高速缓冲是不可见的,这就引发了缓存一致性(可见性)问题。
而且为了处理器内部的运算单元可以被充分利用到,处理器可能回对输入代码进行乱序执行来优化(重排序),这就引发了有序性问题。
JMM(Java内存模型)
JMM的主要目的是定义程序中各种变量的访问规则,即关注在虚拟机量值存储到内存和从内存中取出变量值这一的底层细节(堆中变量(以下所说变量均是如此)的读写操作)。
在JMM中定义,所有的变量都存于主存,每个线程有自己的工作内存,工作内存中应该存有主存中变量的副本(对象的字段或者引用之类),对变量的所有操作都必须在工作内存中进行,而不直接读写主存(valitale也不例外,只是它有内存屏障,迫使线程在主存中读写),不同线程间无法访问彼此的工作线程。
JMM与Java运行时内存区域并不是同一层次的对内存的划分。
JMM中定义了8种操作来完成从主存中读写变量到工作内存。(现已缩减为4种(read,write,lock,unlock)仅描述不同,意义类似)
- lock: 锁定主存变量,把它标识为一条现场独占。
- unlock: 释放主存变量,释放后才可被其他线程获取
- read: 从主存中读值
- load: 将从主存中读来的值宅如工作内存的变量副本中
- use: 把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用这个变量的字节码指令是便会这么做。
- assign: 他把一个执行引擎接受的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时就会执行的操作。
- store: 将工作内存中一个变量的值传送给主内存中
- write: 将从工作内存中得到的值放入主内存变量中。
此外还规定了与之相关的八条规则:
- read与load/store和write必须成套出现,而且顺序执行,但是不一定连续执行(中间有可能插入其他操作)。
- 不允许丢弃它最近的assign操作,即变量在工作内存中改变之后必须把该变化同步回主内存中。
- 一个新的变量只能在主存中诞生,即不允许工作内存直接使用未被初始化的变量,即user、store之前必须assign和load。
- 一个变量同一时刻只允许一条线程对其进行lock操作,但可以被一条线程lock好几次(可重入),对应也需要unlock同样多的次数。
- 在一个变量被lock操作,那将会清空工作内存中该变量的值,使用前会先重新执行assign或load重新初始化。
- 如果一个变量未被一个线程lock那么也不允许unlock。
- 执行unlock之前,先将变量同步回主存(执行store,write操作)
Volatile
保证可见性与禁止重排序(JDK5之后)。
volatile的具体语义
在满足以下条件是可以使用:
- 对其修改对它当前的值没有依赖性
- 不需要与其他变量共同参与不变约束
使用它修饰的变量满足以下特性:
- use前必先load。
- store前必须assign
- 禁止写读重排序
具体内存屏障是用lock addl$0x0,(%esp) 这句指令来实现的。lock前缀他将本处理器的缓存写入主存,该写入动作也会引起其他处理器无效化其对应的缓存,迫使去主存中读取,这就实现了可见性,不过注意这里仍然保存了变量副本,只是基本无用罢了。而前缀后面的是一个空操作。
针对long和double的特殊规则
对64位的基本数据的操作是否具有原子性由虚拟机自行选择。
一个未被声明位volatile的long或者double类型,可能会出现读到既不是原值也不是修改值的,”半个值“。不过出现这种风险的概率极小(32位虚拟机在X86平台下的long类型确实有一些这个风险),除非有明确可知的线程竞争要不然不用特别声明为volatile。
原子性、可见性与有序性
原子性
那八个操作都是能保证原子性的(不过lock和unlock是保证其他代码的原子性),对于基本数据类型的访问读写都是具有原子性的(long和double的非原子协定除外,不过无需过分在意)。
lock和unlock并未直接交给用户,但是开放了更高层的monitorenter和monitorexit。使用synchronized关键字。
可见性
三大同步原语都可以实现,final是因为不可变,synchronized是因为unlock前会将值先刷新到主存,而unlock后其他线程才可以访问。volatile是使用插入内存屏障的方式。
有序性
线程内部,本身就是表现得跟串行化一样的,其他线程视角可能就是乱序的了。
(这个真有点像以前看相对论,本时空参考系下,因果关系是先因后果的,其他时空看着就可能先果后因。)
使用synchronized和volatile关键字来实现。
先行发生原则(Happens-Before)
可见对应部分
以下规则无需任何其他操作就可以保证
- 程序次序规则: 控制流顺序
- 管程锁定规则: unlock时间上后于lock
- volatile变量规则: 写操作先行发生于之后(时间上)的读操作
- 线程终止规则: 线程中的所有操作先行发生于对线程终止的检测。
- 线程中断规则: interrupt方法的调用先行于被中断线程的代码检测到中断事件的发生
- 对象终结规则: 初始化的完成先行发生于它的finalize()方法。
- 传递性: 若A先于B,B先于C,则A先于C
注意: 先行发生并不是指时间上的先后顺序。或者理解成这部分操作不会被重排序到先行之前