702. 如 何 让 Java 的 线 程 彼 此 同 步 ? 你 了 解 过 哪 些 同 步 器 ? 请 分 别 介 绍 下 。

JUC 中 的 同 步 器 三 个 主 要 的 成 员 : CountDownLatch、 CyclicBarrier 和 Semaphore, 通 过 它 们 可 以 方 便 地 实 现 很 多 线 程 之 间 协 作 的 功 能 。
CountDownLatch 叫 倒 计 数 , 允 许 一 个 或 多 个 线 程 等 待 某 些 操 作 完 成 。 看 几 个 场 景 :

  1. 跑 步 比 赛 , 裁 判 需 要 等 到 所 有 的 运 动 员 ( “ 其 他 线 程 ” ) 都 跑 到 终 点( 达 到 目 标 ) , 才 能 去 算 排 名 和 颁 奖 。
  2. 模 拟 并 发 , 我 需 要 启 动 100 个 线 程 去 同 时 访 问 某 一 个 地 址 , 我 希 望 它们 能 同 时 并 发 , 而 不 是 一 个 一 个 的 去 执 行 。

用 法 : CountDownLatch 构 造 方 法 指 明 计 数 数 量 , 被 等 待 线 程 调 用 countDown 将 计 数 器 减 1, 等 待 线 程 使 用 await 进 行 线 程 等 待 。 一 个 简 单 的 例 子 :

CyclicBarrier 叫 循 环 栅 栏 , 它 实 现 让 一 组 线 程 等 待 至 某 个 状 态 之 后 再 全 部 同 时 执 行 , 而 且 当 所 有 等 待 线 程 被 释 放 后 , CyclicBarrier 可 以 被 重 复 使 用 。 CyclicBarrier 的 典 型 应 用 场 景 是 用 来 等 待 并 发 线 程 结 束 。

CyclicBarrier 的 主 要 方 法 是 await(), await() 每 被 调 用 一 次 , 计 数 便 会 减 少 1, 并 阻 塞 住 当 前 线 程 。 当 计 数 减 至 0 时 , 阻 塞 解 除 , 所 有 在 此 CyclicBarrier 上 面 阻 塞 的 线 程 开 始 运 行 。

在 这 之 后 , 如 果 再 次 调 用 await(), 计 数 就 又 会 变 成 N-1, 新 一 轮 重 新开 始 , 这 便 是 Cyclic 的 含 义 所 在 。 CyclicBarrier.await() 带 有 返 回 值 , 用 来 表 示 当 前 线 程 是 第 几 个 到 达 这 个 Barrier 的 线 程 。举 例 说 明 如 下 :

Semaphore, Java 版 本 的 信 号 量 实 现 , 用 于 控 制 同 时 访 问 的 线 程 个 数 , 来 达 到 限 制 通 用 资 源 访 问 的 目 的 , 其 原 理 是 通 过 acquire() 获 取 一 个 许 可 , 如 果 没 有 就 等 待 , 而 release() 释 放 一 个 许 可 。

如 果 Semaphore 的 数 值 被 初 始 化 为 1, 那 么 一 个 线 程 就 可 以 通 过 acquire 进 入 互 斥 状 态 , 本 质 上 和 互 斥 锁 是 非 常 相 似 的 。 但 是 区 别 也 非 常 明 显 , 比 如 互 斥 锁 是 有 持 有 者 的 , 而 对 于 Semaphore 这 种 计 数 器 结 构 , 虽 然 有 类 似 功 能 , 但 其 实 不 存 在 真 正 意 义 的 持 有 者 , 除 非 我 们 进 行 扩 展 包 装 。