1、CPU 术语定义
- 内存屏障(memory barriers):一组处理器指令,用于实现对内存操作的顺序限制。
- 缓冲行(cache line):缓存中可以分配的最小存储单位。
- 原子操作(atomic operations):不可中断的一个或一系列操作。
- 当处理器识别到从内存中读取的操作数是可缓存的,处理器便读取整个缓冲行到适当缓存(L1、L2、L3...)。
- 缓存命中:如果进行缓存的内存位置仍是下次处理器访问的地址时,处理器从缓存中读取,而不是内存。
- 写命中:要写的数据在缓存中,则直接写到缓存,而不是内存。
2、volatile 保证可见性的原理
volatile 修饰的变量在进行写操作时,要执行的汇编指令会有一句 lock 开头的指令,lock 前缀的指令在多核处理器下会引发两件事:
- 将当前处理器缓存行的数据写回到系统内存,这个过程会锁定总线,或者,锁定对应的缓存。
- 该写回内存的操作会使其他CPU缓存了该内存地址的数据无效(缓存一致性协议),下次用到相关数据时,会强强制缓冲行填充。
3、volatile 的使用优化
并发包里面的队列集合类:LinkedTransferQueue ,在使用 volatile 时,用一种追加字节的方式优化队列的出队和入队性能。
原理:
- 一些处理器的 L1、L2、L3 缓存是 64 字节宽,并且不支持部分填充缓存行,当队列的头结点和尾节点不足 64 字节宽时,会被放在同一缓冲行,当某一处理器对头结点处理时,根据缓存一致性原理,会锁定整个缓冲行,导致其他处理器不能对尾节点进行操作,严重影响入队、出队效率。
4、synchronized 实现原理
JVM 基于进入、退出 monitor 对象来实现方法和代码块的同步,两者实现细节不同:
- 代码块同步:使用 monitorenter、monitorexit 指令实现
- 方法同步:JVM 规范里没说,估计也差不多
monitorenter 在编译后插入到同步代码块开始的位置,monitorexit 插入到方法结束和异常处。
5、锁的升级
锁的四种状态:
- 无锁
- 偏向锁:当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里面存储线程ID,以后线程进入、退出同步块时不需要加锁、解锁,只需要检查一下对象头的 Mark Word 是否存在该线程的ID。如果测试成功,则获得锁;失败,则再测试一下 Mark Word 中的偏向锁标识是否设置为1(表示当前是偏向锁),没有设置,则使用 CAS 竞争锁,如果设置了,则尝试使用 CAS 将对象头中的偏向锁指向当前线程。
- 偏向锁的撤销:偏向锁等到竞争出现才会释放锁。
- JAVA6、JAVA7默认应用程序启动几秒钟后启动偏向锁,可以设置参数,马上启动偏向锁。同时,也可以关闭偏向锁。
- 轻量级锁
- 重量级锁
6、原子操作的实现原理
1、总线锁
2、缓存锁
不使用缓存锁的情况:
- 处理器不支持缓存锁
- 操作的数据不能缓存
- 操作的数据跨缓存行
7、JAVA 实现原子操作
1、循环 CAS
- CAS 的三大问题
- ABA 问题
- 循环时间长,开销大
- 只能保证一个共享变量的原子操作
2、锁机制