java并发编程的艺术 - 第二章笔记
术语 | 描述 |
---|---|
memory barries(内存屏障) | 处理器指定,实现对内存操作的顺序限定 |
cache line(缓冲行) | 缓存中的最小存储单位,需要使用多个主内存周期(存储周期?) |
atomic operations(原子操作) | 不可中断的操作 |
cache line fill (缓存行填充) | 处理器读取整个缓存行到适当的缓存 |
cache hit(缓存命中) | 处理器操作地址是缓存行填充的内存地址 |
write hit(写命中) | 写回数据时,如果内存地址在缓存行中,写命中(直接操作缓存行)。 |
volatile
- volatile提供了比synchronized的使用和成本低,不会引起上下文切换和调用
- (原理)转汇编时会多出一行lock汇编代码
- 将当前缓存行写回内存系统
- 写回操作使其他cpu的缓存无效
ps: 缓存一致性协议,每个处理器通过嗅探在总线上传播的数据检查缓存是否过期。发现缓存行的内存被修改后,将缓存行置为无效状态,当处理器对数据修改操作时,把数据从内存读取到处理器缓存。
Lock的处理器原理
缓存回写内存
锁缓存、写回内存、使用缓存一致性确保修改原子性。缓存一致性会组织同时修改由两个以上处理器缓存的内存区域数据。
一个处理器的缓存回写内存会导致其他处理器的相同缓存失效
处理器使用嗅探技术保证它的内部缓存、系统内存和其他处理器的缓存在总线上保持一致。
volatile使用优化
- 追加字节填满缓冲行(Linked-TransferQueue),大多处理器告诉缓存行是64字节。试图修改缓存行数据时,会锁定缓存行。导致其他处理器不能访问锁定的节点。
- 什么时候不能追加到64字节
- 缓存行为32字节的cpu时
- 共享变量不会被频繁写入
ps:java7会淘汰或重新排列无用字段,不能单独使用Object对象来填充
synchronized
- 曾经很重,1.6优化(引入偏向锁和轻量级锁)后,变轻。
- 对于普通方法,锁是当前实例对象
- 静态同步方法,锁是当前类的class对象
- 对于同步方法块(synchonized)
在哪里上锁
synchronized的锁是存在java对象头里面。
ps:对象头:数组类型用三个字宽(多出的一个存放array length);非数组类型,用两个字宽(字宽:Word,32位机1字宽为4字节)
q1:64位虚拟机,在轻量级锁、重量级锁时,mark word的结构?
对象头的mark word
Mark Word默认存储对象的HashCode、分代年龄、锁标记位。但是会随着锁标志位的变化而变化。
锁升级与对比
1.6为了减少获得锁和释放锁的 带来的性能消耗,引入了偏向锁和轻量级锁。因此共有四种锁状态(如下),同时锁只能升级,无法降级。
- 无锁状态
- 偏向锁状态
- 轻量级锁状态
- 重量级锁状态
偏向锁
- 偏向锁撤销:
1. 持有锁的线程不会在同步代码块执行结束后,释放锁。
2. 新线程竞争锁-->暂停持有偏向锁的线程-->检查状态
--live-->恢复无锁、升级偏向锁-- 恢复持有 偏向锁的线程
--dead-->置为无锁 --/
- 关闭偏向锁:
启动几秒后才会激活,关闭延迟:-XX:BiasedLockingStartupDelay=0
如果锁通常处于竞争状态时,可以关闭偏向锁:-XX:-UseBiasedLocking=false
轻量级锁
- 加锁
1. 将对象头中的mark word复制到锁记录(当前线程的栈帧)中。
2. CAS将mark word替换为指向锁记录的指针
3. 成功则获取锁,失败则尝试自旋获取锁
- 解锁
1. CAS将Displaced Mark Word替换回到对象头。
2. 成功则表示无竞争发生;失败则表示存在竞争,膨胀为重量锁。
重量级锁
重量级锁会阻塞获取锁的线程,当持有锁的线程被释放后,才会唤醒这些线程。
原子操作实现原理
cpu的实现
总线锁定
- 使用处理器提供的
LOCK #
信号 - 一个处理器在总线上输出此信号时,其他处理器的请求将会被阻塞住,该处理器可以独占共享内存
ps:总线锁,锁定的是CPU和内存间的通信。
缓存锁
- 不用声明
LOCK #
信号 - 使用缓存一致性机制,来保证操作的原子性
两种情况处理器不会使用缓存锁定
- 数据不能被缓存在处理器内部,操作的数据跨多个缓存行时。使用总线锁定
- 某些处理器不支持缓存锁定。
ps:针对以上机制,通过Intel处理器提供的Lock前缀的指令来实现(位测试和修改指令:BTS、BTR、BTC;交换指令:XADD、CMPXCHG;其他操作指令:ADD、OR)
java的实现
思路:锁和循环CAS
CAS
- CAS利用了处理器提供的CMPXCHG指令实现的。
- 一些问题(ABA、循环时间长开销大、只能保证一个共享变量的操作)
使用锁机制实现原子操作
ps:除了偏向锁,JVM实现锁的方式都采用了循环CAS。