
升级过程无锁 - 偏向锁 - 轻量级锁 - 重量级锁锁标记偏向锁偏向锁的思想是偏向于让第一个获取锁对象的线程这个线程之后重新获取该锁不再需要同步操作当锁对象第一次被线程获得的时候进入偏向状态标记为 101同时使用 CAS 操作将线程 ID 记录到 Mark Word。如果 CAS 操作成功这个线程以后进入这个锁相关的同步块查看这个线程 ID 是自己的就表示没有竞争就不需要再进行任何同步操作当有另外一个线程去尝试获取这个锁对象时偏向状态就宣告结束此时撤销偏向后恢复到未锁定或轻量级锁状态加锁线程第一次进入 synchronized (lock)读取对象 Mark Word判断是无偏向偏向位 0执行 CAS将当前线程 ID Epoch 写入 Mark WordCAS 成功对象变成偏向锁状态绑定当前线程同步代码执行完毕偏向锁不会主动释放线程 ID不会清空。同一线程再次进入同步块核心优势无需 CAS只做两步判断Mark Word 里存储的线程 ID 当前线程 IDEpoch 匹配校验通过直接进入同步代码无任何原子操作性能极高。撤销锁场景 1原偏向线程已退出同步块无活动JVM 到达安全点STW暂停所有线程遍历持有偏向锁的线程栈确认原线程无同步代码清空 Mark Word 中的线程 ID恢复为无偏向状态第二个线程通过 CAS 抢占升级为轻量级锁走自适应自旋逻辑。场景 2原偏向线程还持有锁两个线程同时竞争STW 安全点暂停所有线程检查原线程栈帧找到锁记录撤销偏向将锁直接升级为轻量级锁两个线程通过 CAS 竞争轻量级锁失败线程进入自旋。批量重偏向频繁撤销锁非常耗性能所以设计了批量重偏向在单次撤销次数 20 时每次锁竞争都会伴随一次撤销锁的操作无论是串行竞争还是并行竞争当同一个类的对象发生超过 20 次偏向竞争无论串行还是并行后会触发批量重偏向重偏向会重置对象的 Thread ID当新线程进行串行竞争时只校验对象头不用 STW 撤销直接把对象偏向新线程。阈值参数-XX:BiasedLockingBulkRebiasThreshold20批量撤销同一个类在触发了批量重偏向后25s 内发生了超过 40 次偏向撤销批量重定向后串行竞争已经不会触发撤销操作所以这里是发生了 40 次并发竞争JVM 判断该类锁竞争激烈直接永久关闭这个类所有对象的偏向锁后续新建对象默认无偏向直接走轻量级锁阈值参数-XX:BiasedLockingBulkRevokeThreshold40轻量级锁两个线程交替用锁无长时间阻塞线程在自己的栈帧创建 Lock Record把对象头复制到 Lock Record 中通过 CAS 把对象头换成指向自己 Lock Record 的指针成功拿到锁失败将锁升级为重量级锁然后进入 cxq 链表进行自旋自旋成功则获取锁自旋多次失败则会进入阻塞状态等待唤醒过程创建锁记录Lock Record对象每个线程的栈帧都会包含一个锁记录的结构存储锁定对象的 Mark Word让锁记录中 Object reference 指向锁住的对象并尝试用 CAS 替换 Object 的 Mark Word将 Mark Word 的值存入锁记录如果 CAS 替换成功对象头中存储了锁记录地址和状态 00轻量级锁 表示由该线程给对象加锁如果 CAS 失败有两种情况如果是其它线程已经持有了该 Object 的轻量级锁这时表明有竞争进入锁膨胀过程如果是线程自己执行了 synchronized 锁重入就添加一条 Lock Record 作为重入的计数当退出 synchronized 代码块解锁时如果有取值为 null 的锁记录表示有重入这时重置锁记录表示重入计数减 1如果锁记录的值不为 null这时使用 CAS 将 Mark Word 的值恢复给对象头成功则解锁成功失败说明轻量级锁进行了锁膨胀或已经升级为重量级锁进入重量级锁解锁流程锁膨胀在尝试加轻量级锁的过程中CAS 操作无法成功可能是其它线程为此对象加上了轻量级锁有竞争这时需要进行锁膨胀将轻量级锁变为重量级锁当 Thread-1 进行轻量级加锁时Thread-0 已经对该对象加了轻量级锁Thread-1 加轻量级锁失败进入锁膨胀流程为 Object 对象申请 Monitor 锁通过 Object 对象头获取到持锁线程将 Monitor 的 Owner 置为 Thread-0将 Object 的对象头指向重量级锁地址然后自己进入 Monitor 的 EntryList BLOCKED当 Thread-0 退出同步块解锁时使用 CAS 将 Mark Word 的值恢复给对象头失败这时进入重量级解锁流程即按照 Monitor 地址找到 Monitor 对象设置 Owner 为 null唤醒 EntryList 中 BLOCKED 线程重量级锁monitor每个 Java 对象都可以关联一个 Monitor 对象Monitor 也是 class其实例存储在堆中如果使用 synchronized 给对象上锁重量级之后该对象头的 Mark Word 中就被设置指向 Monitor 对象的指针这就是重量级锁结构过程开始时 Monitor 中 Owner 为 null当 Thread-2 执行 synchronized(obj) 就会将 Monitor 的所有者 Owner 置为 Thread-2Monitor 中只能有一个 Ownerobj 对象的 MarkWord 指向 Monitor在 Thread-2 上锁的过程Thread-3、Thread-4、Thread-5 也执行 synchronized(obj)就会进入 EntryList BLOCKED双向链表Thread-2 执行完同步代码块的内容根据 obj 对象头中 Monitor 地址寻找设置 Owner 为空把线程栈的锁记录中的对象头的值设置回 MarkWord唤醒 EntryList 中等待的线程来竞争锁竞争是非公平的如果这时有新的线程想要获取锁可能直接就抢占到了阻塞队列的线程就会继续阻塞WaitSet 中的 Thread-0是以前获得过锁但条件不满足进入 WAITING 状态的线程自旋优化重量级锁竞争时尝试获取锁的线程不会立即阻塞可以使用自旋来进行优化采用循环的方式去尝试获取锁优点不会进入阻塞状态减少线程上下文切换的消耗缺点当自旋的线程越来越多时会不断的消耗 CPU 资源抢锁成功抢锁失败注意自旋占用 CPU 时间单核 CPU 自旋就是浪费时间因为同一时刻只能运行一个线程多核 CPU 自旋才能发挥优势自旋失败的线程会进入阻塞状态在 Java 6 之后自旋锁是自适应的比如对象刚刚的一次自旋操作成功过那么认为这次自旋成功的可能性会高就多自旋几次反之就少自旋甚至不自旋Java 7 之后不能控制是否开启自旋功能由 JVM 控制