Java中所使用的并发机制依赖于JVM的实现和CPU的指令。
2.1 volatile的应用
1.volatile的定义与实现原理
volatile可以保证变量的可见性。
如何保证?
volatile变量写操作时,会引发两件事:
1)将当前处理器缓存行的数据写回到系统内存。通过缓存一致性协议来保证写操作的原子性。
2)这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效。
这里的第二点如何实现的呢?
通过缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态。在下次访问相同内存地址时,强制执行缓存行填充。
2.volatile的使用优化
深入分析synchronized
synchronized实现同步的基础:Java中的每一个对象都可以作为锁。
2.2.1 Java对象头
synchronized用的锁是存在Java对象头里的。
2.2.2 锁的升级与对比
1.偏向锁
为什么会有偏向锁?
大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而进入了偏向锁。
2.轻量级锁
3.锁的优缺点对比
2.3 原子操作的实现原理
1.术语定义
比较并交换 Compare and Swap CAS操作需要输入两个数值,一个旧值(期望操作前的值)和一个新值,在操作期间比较旧值有没有发生变化,如果没有发生变化,才交换成新的值,发生了变化则不交换。
内存顺序冲突 Memory order violation 内存顺序冲突一般是由假共享引起的,假共享是指多个CPU同时修改同一个缓存行的不同部分而引起其中一个CPU的操作无效,当出现这个内存顺序冲突时,CPU必须清空流水线。
2.处理器如何实现原子操作
首先处理器会自动保证基本的内存操作的原子性。但是复杂的内存操作处理器是不能自动保证其原子性的,比如跨总线宽度、跨多个缓存行和跨页表的访问。但是,处理器提供总线锁定和缓存锁定两个机制来保证复杂内存操作的原子性。
(1) 使用总线锁定保证原子性
所谓总线锁就是使用处理器提供的一个LOCK#信号,当一个处理器在总线上输出此信号时,其他处理器的请求将被阻塞,那么该处理器可以独占共享内存。
(2) 使用缓存锁保证原子性
使用总线锁的时候,其他处理器不能操作其他内存地址的数据,这样,总线锁定的开销大。进而,有了缓存锁定替代总线锁定来进行优化。
所谓“缓存锁定”是指内存区域如果被缓存在处理器的缓存行中,并且在Lock操作期间被锁定,那么当它执行锁操作回写到内存时,处理器不在总线上声言LOCK#信号,而是修改内部的内存地址,并允许它的缓存一致性机制来保证操作的原子性,因为缓存一致性机制会阻止同时修改由两个以上处理器缓存的内存区域数据,当其他处理器回写已被锁定的缓存行的数据时,会使缓存行无效。
3.Java如何实现原子操作
在Java中可以通过锁和循环CAS的方式来实现原子操作。
(1) 使用循环CAS实现原子操作
自旋CAS实现的基本思路就是循环进行CAS操作直到成功为止
(2) CAS实现原子操作的三大问题
ABA问题,循环时间长开销大,以及只能保证一个共享变量的原子操作。
(3) 使用锁机制实现原子操作
锁机制保证了只有获得锁的线程才能够操作锁定的内存区域。JVM内部实现了很多种锁机制,有偏向锁、轻量级锁和互斥锁。有意思的是除了偏向锁,
JVM实现锁的方式都用了循环CAS,即当一个线程想进入同步块的时候使用循环CAS的方式来获取锁,当它退出同步块的时候使用循环CAS释放锁。