第一节 volatile的应用
定义:Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应该确保通过排他锁单独获得这个变量。
为了提高处理速度,处理器不直接和内存进行通信,而是先将系统内存的数据读到内部缓存后再进行操作
在多处理器下, 为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上的传播数据来检查自己缓存的值是不是过期了,当处理器发现自己的缓存行对应的内存地址被修改,就会将当前处理器的缓存设置为无效状态,当处理器对这个数据进行修改操作时,会重新从系统内存中把数据读到处理器缓存里。
volatile两条实现原则:
- Lock前缀指令会引起处理器缓存写回到内存
- 一个处理器的缓存写回到内存会导致其他处理器缓存无效
第二节synchronized的实现原理与应用
synchronized也称为重量级锁,锁的三种形式:
- 对于普通同步方法,锁是当前实例对象
- 对于静态同步方法,锁是当前类的Class对象
- 对于同步代码块,锁是synchronized括号里面配置的对象
代码块同步时使用monitorenter和monitorexit指令实现的,monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处,JVM要保证每个monitorenter必须有monitorexit与之对应。任何对象都有一个monitor与之关联,当且仅当一个monitor被持有后,它将处于锁定状态。
一Java对象头
synchronized使用的锁是存在Java对象头里的。
Java对象头里的Mark Word里默认存储对象的HashCode,分代年龄和锁标记位。
二锁的升级与对比
JavaSE1.6将锁分为4种状态,级别从低到高:无锁状态,偏向锁状态,轻量级锁状态,重量级锁状态。锁只能升级,不能降级。
偏向锁:(为了让线程获得锁的代价更低)当一个线程访问你同步代码块时,首先检查对象头中是否存储了该线程的ID,如果没有使用CAS替换Mark Word,测试对象头中Mark Word中的线程ID是否执行自己,如果只想自己则执行同步体
轻量级锁:在线程执行代码块之前,JVM会分配空间并将对象头中的Mark Word复制到栈帧中,然后线程尝试使用CAS将对象头中的Mark Word替换为执行锁记录的指针,如果成功,将Mark Word替换为轻量级锁,并执行方法体;如果失败,则当前线程尝试使用自旋来获得锁,如果还是获取失败,锁将膨胀为重量级锁。在轻量级锁解锁时,会使用原子的CAS操作将栈中的信息替换回到对象头。
原子操作的实现原理
处理器实现原子操作的方式:
- 通过总线锁保证原子性:当一个处理器在总线上输出LOCK#信号时,其他处理器的请求将会被阻塞住,实现处理器独占内存
- 通过缓存锁保证原子性:
Java实现原子操作的方式:
- 锁:除了偏向锁,JVM实现锁的方式都用了循环CAS
- 循环CAS:循环进行CAS操作直到成功为止
CAS实现原子操作的三大问题:
- ABA 问题(使用compareAndSet方法),可以使用在变量前面追加版本号来解决
- 循环时间长,开销大,使用处理器支持的指令pause
- 只能保证一个共享变量的原子操作,AtomicReference类可以保证引用对象之间的原子性