Synchronized 相 关 问 题
问 题 一 : Synchronized 用 过 吗 , 其 原 理 是 什 么 ?
这 一 道 Java 面 试 中 几 乎 百 分 百 会 问 到 的 问 题 , 因 为 没 有 任 何 写 过 并
发 程 序 的 开 发 者 会 没 听 说 或 者 没 接 触 过 Synchronized。
Synchronized 是 由 JVM 实 现 的 一 种 实 现 互 斥 同 步 的 一 种 方 式 , 如 果
你 查 看 被 Synchronized 修 饰 过 的 程 序 块 编 译 后 的 字 节 码 , 会 发 现 ,
被 Synchronized 修 饰 过 的 程 序 块 , 在 编 译 前 后 被 编 译 器 生 成 了
monitorenter 和 monitorexit 两 个 字 节 码 指 令 。
这 两 个 指 令 是 什 么 意 思 呢 ?
在 虚 拟 机 执 行 到 monitorenter 指 令 时 , 首 先 要 尝 试 获 取 对 象 的 锁 :
如 果 这 个 对 象 没 有 锁 定 , 或 者 当 前 线 程 已 经 拥 有 了 这 个 对 象 的 锁 , 把 锁
的 计 数 器 +1; 当 执 行 monitorexit 指 令 时 将 锁 计 数 器 -1; 当 计 数 器
为 0 时 , 锁 就 被 释 放 了 。
如 果 获 取 对 象 失 败 了 , 那 当 前 线 程 就 要 阻 塞 等 待 , 直 到 对 象 锁 被 另 外 一
个 线 程 释 放 为 止 。
Java 中 Synchronize 通 过 在 对 象 头 设 置 标 记 , 达 到 了 获 取 锁 和 释 放
锁 的 目 的 。
问 题 二 : 你 刚 才 提 到 获 取 对 象 的 锁 , 这 个 “ 锁 ” 到 底 是 什 么 ? 如 何 确 定
对 象 的 锁 ?
“ 锁 ” 的 本 质 其 实 是 monitorenter 和 monitorexit 字 节 码 指 令 的 一
个 Reference 类 型 的 参 数 , 即 要 锁 定 和 解 锁 的 对 象 。 我 们 知 道 , 使 用
Synchronized 可 以 修 饰 不 同 的 对 象 , 因 此 , 对 应 的 对 象 锁 可 以 这 么 确
定 。
- 如 果 Synchronized 明 确 指 定 了 锁 对 象 , 比 如 Synchronized( 变 量
名 ) 、 Synchronized(this) 等 , 说 明 加 解 锁 对 象 为 该 对 象 。 - 如 果 没 有 明 确 指 定 :
若 Synchronized 修 饰 的 方 法 为 非 静 态 方 法 , 表 示 此 方 法 对 应 的 对 象 为
锁 对 象 ;
若 Synchronized 修 饰 的 方 法 为 静 态 方 法 , 则 表 示 此 方 法 对 应 的 类 对 象
为 锁 对 象 。
注 意 , 当 一 个 对 象 被 锁 住 时 , 对 象 里 面 所 有 用 Synchronized 修 饰 的
方 法 都 将 产 生 堵 塞 , 而 对 象 里 非 Synchronized 修 饰 的 方 法 可 正 常 被
调 用 , 不 受 锁 影 响 。
问 题 三 : 什 么 是 可 重 入 性 , 为 什 么 说 Synchronized 是 可 重 入 锁 ?
可 重 入 性 是 锁 的 一 个 基 本 要 求 , 是 为 了 解 决 自 己 锁 死 自 己 的 情 况 。
一 个 类 中 的 同 步 方 法 调 用 另 一 个 同 步 方 法 , 假 如
Synchronized 不 支 持 重 入 , 进 入 method2 方 法 时 当 前 线 程 获 得 锁 ,
method2 方 法 里 面 执 行 method1 时 当 前 线 程 又 要 去 尝 试 获 取 锁 , 这
时 如 果 不 支 持 重 入 , 它 就 要 等 释 放 , 把 自 己 阻 塞 , 导 致 自 己 锁 死 自 己 。
· 点 击 图 片 , 放 大 查 看 · 对 Synchronized 来 说 , 可 重 入 性 是 显 而 易 见 的 , 刚 才 提 到 , 在 执 行
monitorenter 指 令 时 , 如 果 这 个 对 象 没 有 锁 定 , 或 者 当 前 线 程 已 经 拥
有 了 这 个 对 象 的 锁 ( 而 不 是 已 拥 有 了 锁 则 不 能 继 续 获 取 ) , 就 把 锁 的 计
数 器 +1, 其 实 本 质 上 就 通 过 这 种 方 式 实 现 了 可 重 入 性 。
问 题 四 : JVM 对 Java 的 原 生 锁 做 了 哪 些 优 化 ?
在 Java 6 之 前 , Monitor 的 实 现 完 全 依 赖 底 层 操 作 系 统 的 互 斥 锁 来
实 现 , 也 就 是 我 们 刚 才 在 问 题 二 中 所 阐 述 的 获 取 /释 放 锁 的 逻 辑 。
由 于 Java 层 面 的 线 程 与 操 作 系 统 的 原 生 线 程 有 映 射 关 系 , 如 果 要 将 一
个 线 程 进 行 阻 塞 或 唤 起 都 需 要 操 作 系 统 的 协 助 , 这 就 需 要 从 用 户 态 切 换
到 内 核 态 来 执 行 , 这 种 切 换 代 价 十 分 昂 贵 , 很 耗 处 理 器 时 间 , 现 代 JDK
中 做 了 大 量 的 优 化 。
一 种 优 化 是 使 用 自 旋 锁 , 即 在 把 线 程 进 行 阻 塞 操 作 之 前 先 让 线 程 自 旋 等
待 一 段 时 间 , 可 能 在 等 待 期 间 其 他 线 程 已 经 解 锁 , 这 时 就 无 需 再 让 线 程
执 行 阻 塞 操 作 , 避 免 了 用 户 态 到 内 核 态 的 切 换 。
现 代 JDK 中 还 提 供 了 三 种 不 同 的 Monitor 实 现 , 也 就 是 三 种 不 同 的
锁 :
1.偏 向 锁 ( Biased Locking)
- 轻 量 级 锁
3.重 量 级 锁
这 三 种 锁 使 得 JDK 得 以 优 化 Synchronized 的 运 行 , 当 JVM 检 测
到 不 同 的 竞 争 状 况 时 , 会 自 动 切 换 到 适 合 的 锁 实 现 , 这 就 是 锁 的 升 级 、
降 级 。
当 没 有 竞 争 出 现 时 , 默 认 会 使 用 偏 向 锁 。
JVM 会 利 用 CAS 操 作 , 在 对 象 头 上 的 Mark Word 部 分 设 置 线 程
ID, 以 表 示 这 个 对 象 偏 向 于 当 前 线 程 , 所 以 并 不 涉 及 真 正 的 互 斥 锁 , 因
为 在 很 多 应 用 场 景 中 , 大 部 分 对 象 生 命 周 期 中 最 多 会 被 一 个 线 程 锁 定 ,
使 用 偏 斜 锁 可 以 降 低 无 竞 争 开 销 。
1.如 果 有 另 一 线 程 试 图 锁 定 某 个 被 偏 斜 过 的 对 象 , JVM 就 撤 销 偏 斜 锁 ,
切 换 到 轻 量 级 锁 实 现 。
轻 量 级 锁 依 赖 CAS 操 作 Mark Word 来 试 图 获 取 锁 , 如 果 重 试 成 功 ,
就 使 用 普 通 的 轻 量 级 锁 ; 否 则 , 进 一 步 升 级 为 重 量 级 锁 。
问 题 五 : 为 什 么 说 Synchronized 是 非 公 平 锁 ?
非 公 平 主 要 表 现 在 获 取 锁 的 行 为 上 , 并 非 是 按 照 申 请 锁 的 时 间 前 后 给 等
待 线 程 分 配 锁 的 , 每 当 锁 被 释 放 后 , 任 何 一 个 线 程 都 有 机 会 竞 争 到 锁 ,
这 样 做 的 目 的 是 为 了 提 高 执 行 性 能 , 缺 点 是 可 能 会 产 生 线 程 饥 饿 现 象 。