第二章:Java并发机制的底层实现原理
- Java代码在编译之后会生成字节码文件,也就是.class文件。class文件经过类加载器的加载到JVM中,JVM解释字节码文件,最终转化为汇编指令在CPU上执行。Java中所使用的并发机制依赖于JVM的实现和CPU的指令
2:volatile
- 保证了所有变量的内存可见性
- 修改完之后,就会立即写入内存,并且告诉各个CPU中的缓存失效
- 汇编指令:LOCK
- 将当前的缓存行写入内存中
- 锁总线,或者是锁缓存
- 告诉其他的COU缓存的数据无效
3:synchronized
1:锁的对象
- 普通的同步方法,锁的是该实例
- static静态方法,锁的是class对象
- 同步方法块,锁的是括号中的对象
2:原理
- JVM基于进入和退出monitor对象来实现方法同步和代码块进行同步。代码块的同步时使用monitorenter和monitorexit指令进行实现的,但是方法的同步不是。
- monitorenter指令是在编译后插入到同步代码块开始的位置,monitorexit是在方法结尾的地方,当有一个monitor被持有的时候,他就会处于锁定的状态。线程执行到monitor的时候,就会尝试获得对象所持有的锁。
3:Java对象头
- mark word 对象头
- hashcod GC年龄 锁(一位是偏向锁,两位锁的标志)
- 偏向锁:线程ID + Epoch + GC年龄 + 1 + 01
- Epoch表示重偏向了几次,记录偏向锁被撤销的次数,当次数达到阈值的时候,就不会分配偏向锁。
- 轻量级锁:指向栈中锁记录的指针 + 00
- 重量级锁:指向互斥量的指针 + 10
- GC标记:空 + 11
- 存储该对象类型数据的指针
4:锁的升级
- 偏向锁
- 对象头和锁记录中存放偏向的线程ID
- 轻量级锁
- JVM会在当前的线程栈中创建用于存储锁记录的空间,并将对象头中的mark word复制到锁记录中,即displaced mark word。
- 然后线程进行CAS将对象头中的mark word替换为指向锁记录的指针,如果成功,当前的线程获得锁。
- 重量级锁
- 线程堵塞,响应时间缓慢
5:实现原子操作
-
总线加锁
- 处理器提供一个LOCK信号,当一个处理器在总线上输出此信号的时候,其他处理器的请求将会被阻塞,该处理器可以独占共享内存
-
缓存锁
- 写回的时候,不再发送LOCK信号,而是修改内部的内存地址,并通过缓存一致性的机制来保证操作的原子性。阻止两个以上的CPU同时修改自己的缓存信息
- 数据不能被缓存在处理器内部的时候,或者是跨多个缓存行的时候,就不能使用缓存锁
- 有一些处理器不支持缓存锁定
- 写回的时候,不再发送LOCK信号,而是修改内部的内存地址,并通过缓存一致性的机制来保证操作的原子性。阻止两个以上的CPU同时修改自己的缓存信息
6:Java实现原子操作
- CAS
- CAMXCHG
- 三大问题
- ABA
- 循环性能问题
- 只能改变单个变量
- 锁机制