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

  • 如果是自己执行了 synchronized 锁重入 , 那么再添加一条 Lock Record 作为重入的计数
  • 此外最后的cas恢复操作也不是都成功的:
    • 成功,则解锁成功
    • 失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程
    我们给出锁重入时的展示图:
    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}}我们给出简单示例图:
    graph LRsubgraph 偏向锁t5("m1内调用synchronized(obj)")t6("m2内调用synchronized(obj)")t7("m2内调用synchronized(obj)")t8(对象)t5 -.用ThreadID替换MarkWord.-> t8t6 -.检查ThreadID是否是自己.-> t8t7 -.检查ThreadID是否是自己.-> t8endsubgraph 轻量级锁t1("m1内调用synchronized(obj)")t2("m2内调用synchronized(obj)")t3("m2内调用synchronized(obj)")t1 -.生成锁记录.-> t1t2 -.生成锁记录.-> t2t3 -.生成锁记录.-> t3t4(对象)t1 -.用锁记录替换markword.-> t4t2 -.用锁记录替换markword.-> t4t3 -.用锁记录替换markword.-> t4end偏向状态

    推荐阅读