并发编程面试题二

**

可重入锁 ReentrantLock 及其他显式锁相关问题

问 题 一 : 跟 Synchronized 相 比 , 可 重 入 锁 ReentrantLock 其 实 现
原 理 有 什 么 不 同
?**
其 实 , 锁 的 实 现 原 理 基 本 是 为 了 达 到 一 个 目 的 :
让 所 有 的 线 程 都 能 看 到 某 种 标 记 。
Synchronized 通 过 在 对 象 头 中 设 置 标 记 实 现 了 这 一 目 的 , 是 一 种 JVM
原 生 的 锁 实 现 方 式 , 而 ReentrantLock 以 及 所 有 的 基 于 Lock 接 口 的
实 现 类 , 都 是 通 过 用 一 个 volitile 修 饰 的 int 型 变 量 , 并 保 证 每 个 线
程 都 能 拥 有 对 该 int 的 可 见 性 和 原 子 修 改 , 其 本 质 是 基 于 所 谓 的 AQS框 架

问 题 二 : 那 么 请 谈 谈 AQS 框 架 是 怎 么 回 事 儿 ?
AQS( AbstractQueuedSynchronizer 类 ) 是 一 个 用 来 构 建 锁 和 同 步 器
的 框 架 , 各 种 Lock 包 中 的 锁 ( 常 用 的 有 ReentrantLock、
ReadWriteLock) , 以 及 其 他 如 Semaphore、 CountDownLatch, 甚
至 是 早 期 的 FutureTask 等 , 都 是 基 于 AQS 来 构 建

  1. AQS 在 内 部 定 义 了 一 个 volatile int state 变 量 , 表 示 同 步 状 态 : 当 线程 调 用 lock 方 法 时 , 如 果 state=0, 说 明 没 有 任 何 线 程 占 有 共 享 资 源的 锁 , 可 以 获 得 锁 并 将 state=1; 如 果 state=1, 则 说 明 有 线 程 目 前 正 在使 用 共 享 变 量 , 其 他 线 程 必 须 加 入 同 步 队 列 进 行 等 待 。
  2. AQS 通 过 Node 内 部 类 构 成 的 一 个 双 向 链 表 结 构 的 同 步 队 列 , 来 完 成 线程 获 取 锁 的 排 队 工 作 , 当 有 线 程 获 取 锁 失 败 后 , 就 被 添 加 到 队 列 末 尾 。o Node 类 是 对 要 访 问 同 步 代 码 的 线 程 的 封 装 , 包 含 了 线 程 本 身 及 其 状 态 叫waitStatus( 有 五 种 不 同 取 值 , 分 别 表 示 是 否 被 阻 塞 , 是 否 等 待 唤 醒 ,是 否 已 经 被 取 消 等 ) , 每 个 Node 结 点 关 联 其 prev 结 点 和 next 结点 , 方 便 线 程 释 放 锁 后 快 速 唤 醒 下 一 个 在 等 待 的 线 程 , 是 一 个 FIFO 的 过程 。
    o Node 类 有 两 个 常 量 , SHARED 和 EXCLUSIVE, 分 别 代 表 共 享 模 式 和 独占 模 式 。 所 谓 共 享 模 式 是 一 个 锁 允 许 多 条 线 程 同 时 操 作 ( 信 号 量Semaphore 就 是 基 于 AQS 的 共 享 模 式 实 现 的 ) , 独 占 模 式 是 同 一 个 时间 段 只 能 有 一 个 线 程 对 共 享 资 源 进 行 操 作 , 多 余 的 请 求 线 程 需 要 排 队 等 待
    ( 如 ReentranLock) 。
  3. AQS 通 过 内 部 类 ConditionObject 构 建 等 待 队 列 ( 可 有 多 个 ) , 当
    Condition 调 用 wait() 方 法 后 , 线 程 将 会 加 入 等 待 队 列 中 , 而 当
    Condition 调 用 signal() 方 法 后 , 线 程 将 从 等 待 队 列 转 移 动 同 步 队 列 中进 行 锁 竞 争 。
  4. AQS 和 Condition 各 自 维 护 了 不 同 的 队 列 , 在 使 用 Lock 和
    Condition 的 时 候 , 其 实 就 是 两 个 队 列 的 互 相 移 动 。

问 题 三 : 请 尽 可 能 详 尽 地 对 比 下 Synchronized 和 ReentrantLock
的 异 同 。

ReentrantLock 是 Lock 的 实 现 类 , 是 一 个 互 斥 的 同 步 锁 。
从 功 能 角 度 , ReentrantLock 比 Synchronized 的 同 步 操 作 更 精 细
( 因 为 可 以 像 普 通 对 象 一 样 使 用 ) , 甚 至 实 现 Synchronized 没 有 的
高 级 功 能 , 如 :
 等 待 可 中 断 : 当 持 有 锁 的 线 程 长 期 不 释 放 锁 的 时 候 , 正 在 等 待 的 线 程 可以 选 择 放 弃 等 待 , 对 处 理 执 行 时 间 非 常 长 的 同 步 块 很 有 用 。
 带 超 时 的 获 取 锁 尝 试 : 在 指 定 的 时 间 范 围 内 获 取 锁 , 如 果 时 间 到 了 仍 然无 法 获 取 则 返 回 。
 可 以 判 断 是 否 有 线 程 在 排 队 等 待 获 取 锁 。
 可 以 响 应 中 断 请 求 : 与 Synchronized 不 同 , 当 获 取 到 锁 的 线 程 被 中断 时 , 能 够 响 应 中 断 , 中 断 异 常 将 会 被 抛 出 , 同 时 锁 会 被 释 放 。
 可 以 实 现 公 平 锁 。
从 锁 释 放 角 度 , Synchronized 在 JVM 层 面 上 实 现 的 , 不 但 可 以 通 过
一 些 监 控 工 具 监 控 Synchronized 的 锁 定 , 而 且 在 代 码 执 行 出 现 异 常
时 , JVM 会 自 动 释 放 锁 定 ; 但 是 使 用 Lock 则 不 行 , Lock 是 通 过 代
码 实 现 的 , 要 保 证 锁 定 一 定 会 被 释 放 , 就 必 须 将 unLock() 放 到
finally{} 中 。
从 性 能 角 度 , Synchronized 早 期 实 现 比 较 低 效 , 对 比
ReentrantLock, 大 多 数 场 景 性 能 都 相 差 较 大 。
但 是 在 Java 6 中 对 其 进 行 了 非 常 多 的 改 进 , 在 竞 争 不 激 烈 时 ,
Synchronized 的 性 能 要 优 于 ReetrantLock; 在 高 竞 争 情 况 下 ,
Synchronized 的 性 能 会 下 降 几 十 倍 , 但 是 ReetrantLock 的 性 能 能 维
持 常 态 。

问 题 四 : ReentrantLock 是 如 何 实 现 可 重 入 性 的 ?
ReentrantLock 内 部 自 定 义 了 同 步 器 Sync( Sync 既 实 现 了 AQS,
又 实 现 了 AOS, 而 AOS 提 供 了 一 种 互 斥 锁 持 有 的 方 式 ) , 其 实 就 是
加 锁 的 时 候 通 过 CAS 算 法 , 将 线 程 对 象 放 到 一 个 双 向 链 表 中 , 每 次 获取 锁 的 时 候 , 看 下 当 前 维 护 的 那 个 线 程 ID 和 当 前 请 求 的 线 程 ID 是 否一 样 , 一 样 就 可 重 入 了 。

问 题 五 : 除 了 ReetrantLock, 你 还 接 触 过 JUC 中 的 哪 些 并 发 工 具 ?
通 常 所 说 的 并 发 包 ( JUC) 也 就 是 java.util.concurrent 及 其 子 包 , 集
中 了 Java 并 发 的 各 种 基 础 工 具 类 , 具 体 主 要 包 括 几 个 方 面 :
 提 供 了 CountDownLatch、 CyclicBarrier、 Semaphore 等 , 比
Synchronized 更 加 高 级 , 可 以 实 现 更 加 丰 富 多 线 程 操 作 的 同 步 结 构 。
 提 供 了 ConcurrentHashMap、 有 序 的 ConcunrrentSkipListMap, 或
者 通 过 类 似 快 照 机 制 实 现 线 程 安 全 的 动 态 数 组 CopyOnWriteArrayList
等 , 各 种 线 程 安 全 的 容 器 。
 提 供 了 ArrayBlockingQueue、 SynchorousQueue 或 针 对 特 定 场 景 的
PriorityBlockingQueue 等 , 各 种 并 发 队 列 实 现 。
 强 大 的 Executor 框 架 , 可 以 创 建 各 种 不 同 类 型 的 线 程 池 , 调 度 任 务 运行 等 。

问 题 六 : 请 谈 谈 ReadWriteLock 和 StampedLock。
虽 然 ReentrantLock 和 Synchronized 简 单 实 用 , 但 是 行 为 上 有 一
定 局 限 性 , 要 么 不 占 , 要 么 独 占 。 实 际 应 用 场 景 中 , 有 时 候 不 需 要 大 量竞 争 的 写 操 作 , 而 是 以 并 发 读 取 为 主 , 为 了 进 一 步 优 化 并 发 操 作 的 粒度 , Java 提 供 了 读 写 锁 。
读 写 锁 基 于 的 原 理 是 多 个 读 操 作 不 需 要 互 斥 , 如 果 读 锁 试 图 锁 定 时 , 写锁 是 被 某 个 线 程 持 有 , 读 锁 将 无 法 获 得 , 而 只 好 等 待 对 方 操 作 结 束 , 这样 就 可 以 自 动 保 证 不 会 读 取 到 有 争 议 的 数 据 。
ReadWriteLock 代 表 了 一 对 锁 , 下 面 是 一 个 基 于 读 写 锁 实 现 的 数 据 结构 , 当 数 据 量 较 大 , 并 发 读 多 、 并 发 写 少 的 时 候 , 能 够 比 纯 同 步 版 本 凸显 出 优 势 :

并发编程面试题二

读 写 锁 看 起 来 比 Synchronized 的 粒 度 似 乎 细 一 些 , 但 在 实 际 应 用
中 , 其 表 现 也 并 不 尽 如 人 意 , 主 要 还 是 因 为 相 对 比 较 大 的 开 销 。
所 以 , JDK 在 后 期 引 入 了 StampedLock, 在 提 供 类 似 读 写 锁 的 同 时 ,
还 支 持 优 化 读 模 式 。 优 化 读 基 于 假 设 , 大 多 数 情 况 下 读 操 作 并 不 会 和 写操 作 冲 突 , 其 逻 辑 是 先 试 着 修 改 , 然 后 通 过 validate 方 法 确 认 是 否 进入 了 写 模 式 , 如 果 没 有 进 入 , 就 成 功 避 免 了 开 销 ; 如 果 进 入 , 则 尝 试 获取 读 锁

并发编程面试题二

问 题 七 : CyclicBarrier 和 CountDownLatch 看 起 来 很 相 似 , 请 对 比
下 呢 ?

它 们 的 行 为 有 一 定 相 似 度 , 区 别 主 要 在 于 :
 CountDownLatch 是 不 可 以 重 置 的 , 所 以 无 法 重 用 , CyclicBarrier 没
有 这 种 限 制 , 可 以 重 用 。
 CountDownLatch 的 基 本 操 作 组 合 是 countDown/await, 调 用
await 的 线 程 阻 塞 等 待 countDown 足 够 的 次 数 , 不 管 你 是 在 一 个 线
程 还 是 多 个 线 程 里 countDown, 只 要 次 数 足 够 即 可 。 CyclicBarrier
的 基 本 操 作 组 合 就 是 await, 当 所 有 的 伙 伴 都 调 用 了 await, 才 会 继 续
进 行 任 务 , 并 自 动 进 行 重 置 。
CountDownLatch 目 的 是 让 一 个 线 程 等 待 其 他 N 个 线 程 达 到 某 个 条
件 后 , 自 己 再 去 做 某 个 事 ( 通 过 CyclicBarrier 的 第 二 个 构 造 方 法
public CyclicBarrier(int parties, Runnable barrierAction), 在 新 线
程 里 做 事 可 以 达 到 同 样 的 效 果 ) 。 而 CyclicBarrier 的 目 的 是 让 N 多
线 程 互 相 等 待 直 到 所 有 的 都 达 到 某 个 状 态 , 然 后 这 N 个 线 程 再 继 续 执行 各 自 后 续 ( 通 过 CountDownLatch 在 某 些 场 合 也 能 完 成 类 似 的 效
果 ) 。