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