JUC学习笔记——共享模型之管程( 九 )

我们给出锁重入时的展示图:

JUC学习笔记——共享模型之管程

文章插图
锁膨胀我们首先简单介绍一下锁膨胀:
  • 如果在尝试加轻量级锁的过程中,CAS 操作无法成功,这时一种情况就是有其它线程为此对象加上了轻量级锁(有竞争)
  • 这时需要进行锁膨胀,将轻量级锁变为重量级锁 。
我们给出部分内容展示:
  • 当 Thread-1 进行轻量级加锁时,Thread-0 已经对该对象加了轻量级锁

JUC学习笔记——共享模型之管程

文章插图
  • 这时 Thread-1 加轻量级锁失败,进入锁膨胀流程
  • 即为 Object 对象申请 Monitor 锁,让 Object 指向重量级锁地址 ,然后自己进入 Monitor 的 EntryList BLOCKED

JUC学习笔记——共享模型之管程

文章插图
  • 当 Thread-0 退出同步块解锁时,使用 cas 将 Mark Word 的值恢复给对象头,失败 。
  • 这时会进入重量级解锁 流程,即按照 Monitor 地址找到 Monitor 对象,设置 Owner 为 null,唤醒 EntryList 中 BLOCKED 线程
自旋优化我们同样先来介绍一下自旋优化:
  • 重量级锁竞争的时候,还可以使用自旋来进行优化
  • 如果当前线程自旋成功(即这时候持锁线程已经退出了同步块,释放了锁),这时当前线程就可以避免阻塞 。
我们给出自旋成功的案例:
线程1 (core 1上)对象Mark线程2 ( core 2上)-10(重量锁)-访问同步块,获取monitor10(重量锁)重量锁指针-成功(加锁)10(重量锁)重量锁指针-执行同步块10(重量锁)重量锁指针-执行同步块10(重量锁)重量锁指针访问同步块,获取 monitor执行同步块10(重量锁)重量锁指针自旋重试执行完毕10(重量锁)重量锁指针自旋重试成功(解锁)01(无锁)自旋重试-10(重量锁)重量锁指针成功(加锁)-10(重量锁)重量锁指针执行同步块-......我们也给出自旋失败的案例:
线程1 ( core 1上)对象Mark线程2( core 2上)-10(重量锁)-访问同步块,获取monitor10(重量锁)重量锁指针-成功(加锁)10(重量锁)重量锁指针-执行同步块10(重量锁)重量锁指针-执行同步块10(重量锁)重量锁指针访问同步块,获取monitor执行同步块10(重量锁)重量锁指针自旋重试执行同步块10(重量锁)重量锁指针自旋重试执行同步块10(重量锁)重量锁指针自旋重试执行同步块10(重量锁)重量锁指针阻塞-......我们针对自旋添加一些注意信息:
  • Java 7 之后不能控制是否开启自旋功能
  • 自旋会占用 CPU 时间,单核 CPU 自旋就是浪费,多核 CPU 自旋才能发挥优势 。
  • 在 Java 6 之后自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会高,就多自旋几次
  • 在 Java 6 之后自旋锁是自适应的,比如对象刚刚的一次自旋操作失败,那么就认为这次自旋成功的可能性会低,少自旋甚至不自旋
偏向锁我们再来介绍一下偏向锁:
  • 轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行 CAS 操作 。
  • 偏向锁:只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现 这个线程ID是自己的表示没有竞争,不用重新 CAS 。
  • 以后只要不发生竞争,这个对象就归该线程所有
我们给出一个简单实例:
// 下述三个操作完全不具有时间冲突,相当于全由m1来管辖,这时m1使用会进行cas更换,后续m2,m3均不会进行cas切换static final Object obj = new Object();public static void m1() {synchronized( obj ) {// 同步块 Am2();}}public static void m2() {synchronized( obj ) {// 同步块 Bm3();}}public static void m3() {synchronized( obj ) {// 同步块 C}}

经验总结扩展阅读