synchronized 锁的升级过程

偏向锁
HotSpot 的作者 经过 研究 发现 ,大多数情况下, 锁 不 仅 不存在多 线 程 竞 争,而且 总 是由同
一 线 程多次 获 得, 为 了 让线 程 获 得 锁 的代价更低而引入了偏向 锁 。当一个 线 程 访问 同步 块 并
获 取 锁时 ,会在 对 象 头 和 栈帧 中的 锁记录 里存 储锁 偏向的 线 程 ID ,以后 该线 程在 进 入和退出
同步 块时 不需要 进 行 CAS 操作来加 锁 和解 锁 ,只需 简单 地 测试 一下 对 象 头 的 Mark Word 里是否
存 储 着指向当前 线 程的偏向 锁 。如果 测试 成功,表示 线 程已 经获 得了 锁 。如果 测试 失 败 , 则 需
要再 测试 一下 Mark Word 中偏向 锁 的 标识 是否 设 置成 1 (表示当前是偏向 锁 ):如果没有 设 置, 则
使用 CAS 竞 争 锁 ;如果 设 置了, 则尝试 使用 CAS 将 对 象 头 的偏向 锁 指向当前 线 程。
( 1 )偏向 锁 的撤 销
偏向 锁 使用了一种等到 竞 争出 现 才 释 放 锁 的机制,所以当其他 线 程 尝试竞 争偏向 锁时 ,
持有偏向 锁 的 线 程才会 释 放 锁 。偏向 锁 的撤 销 ,需要等待全局安全点(在 这 个 时间 点上没有正
在 执 行的字 节码 )。它会首先 暂 停 拥 有偏向 锁 的 线 程,然后 检查 持有偏向 锁 的 线 程是否活着,
如果 线 程不 处 于活 动 状 态 , 则 将 对 象 头设 置成无 锁 状 态 ;如果 线 程仍然活着, 拥 有偏向 锁 的 栈
会被 执 行,遍 历 偏向 对 象的 锁记录 , 栈 中的 锁记录 和 对 象 头 的 Mark Word 要么重新偏向于其他
线 程,要么恢复到无 锁 或者 标记对 象不适合作 为 偏向 锁 ,最后 唤 醒 暂 停的 线 程。 图 2-1 中的 线
程 1 演示了偏向 锁 初始化的流程, 线 程 2 演示了偏向 锁 撤 销 的流程。
( 2 )关 闭 偏向 锁 偏向 锁 在 Java 6 和 Java 7 里是默 认 启用的,但是它在 应 用程序启 动 几秒 钟 之后才激活,如 有必要可以使用JVM 参数来关 闭 延 迟 : -XX:BiasedLockingStartupDelay=0 。如果你确定 应 用程 序里所有的锁 通常情况下 处 于 竞 争状 态 ,可以通 过 JVM 参数关 闭 偏向 锁 : -XX:- UseBiasedLocking=false,那么程序默 认 会 进 入 轻 量 级锁 状 态 。

 

轻量级锁
( 1 ) 轻 量 级锁 加 锁
线 程在 执 行同步 块 之前, JVM 会先在当前 线 程的 栈桢 中 创 建用于存 储锁记录 的空 间 ,并
将 对 象 头 中的 Mark Word 复制到 锁记录 中,官方称 为 Displaced Mark Word 。然后 线 程 尝试 使用
CAS 将 对 象 头 中的 Mark Word 替 换为 指向 锁记录 的指 针 。如果成功,当前 线 程 获 得 锁 ,如果失
败 ,表示其他 线 程 竞 争 锁 ,当前 线 程便 尝试 使用自旋来 获 取 锁 。
( 2 ) 轻 量 级锁 解 锁
轻 量 级 解 锁时 ,会使用原子的 CAS 操作将 Displaced Mark Word 替 换 回到 对 象 头 ,如果成
功, 则 表示没有 竞 争 发 生。如果失 败 ,表示当前 锁 存在 竞 争, 锁 就会膨 胀 成重量 级锁 。 图 2-2 是
两个 线 程同 时 争 夺锁 , 导 致 锁 膨 胀 的流程 图 。

锁的优缺点对比

 

posted @ 2022-08-05 17:35  liuhuayiye  阅读(360)  评论(0编辑  收藏  举报