并发编程面试(四)Java内存模型相关问题
什 么 是 Java 的 内 存 模 型 , Java 中 各 个 线 程 是 怎 么 彼 此 看 到 对 方 的 变 量 的 ?
java 的 内 存 模 型 定 义 了 程 序 中 各 个 变 量 的 访 问 规 则 , 即 在 虚 拟 机 中 将 变 量 存 储 到 内 存 和 从 内 存 中 取 出 这 样 的 底 层 细 节 。
此 处 的 变 量 包 括 实 例 字 段 、 静 态 字 段 和 构 成 数 组 对 象 的 元 素 , 但 是 不 包 括 局 部 变 量 和 方 法 参 数 , 因 为 这 些 是 线 程 私 有 的 , 不 会 被 共 享 , 所 以 不 存 在 竞 争 问 题 。
Java 中 各 个 线 程 是 怎 么 彼 此 看 到 对 方 的 变 量 的 呢 ? Java 中 定 义 了 主 内 存 与 工 作 内 存 的 概 念 :
所 有 的 变 量 都 存 储 在 主 内 存 , 每 条 线 程 还 有 自 己 的 工 作 内 存 , 保 存 了 被 该 线 程 使 用 到 的 变 量 的 主 内 存 副 本 拷 贝 。
线 程 对 变 量 的 所 有 操 作 ( 读 取 、 赋 值 ) 都 必 须 在 工 作 内 存 中 进 行 , 不 能 直 接 读 写 主 内 存 的 变 量 。 不 同 的 线 程 之 间 也 无 法 直 接 访 问 对 方 工 作 内 存 的 变 量 , 线 程 间 变 量 值 的 传 递 需 要 通 过 主 内 存 。
请 谈 谈 volatile 有 什 么 特 点 , 为 什 么 它 能 保 证 变 量 对 所 有 线 程 的 可 见 性 ?
关 键 字 volatile 是 Java 虚 拟 机 提 供 的 最 轻 量 级 的 同 步 机 制 。 当 一 个 变 量 被 定 义 成 volatile 之 后 , 具 备 两 种 特 性 :
-
保 证 此 变 量 对 所 有 线 程 的 可 见 性 。 当 一 条 线 程 修 改 了 这 个 变 量 的 值 , 新 值 对 于 其 他 线 程 是 可 以 立 即 得 知 的 。 而 普 通 变 量 做 不 到 这 一 点 。
-
禁 止 指 令 重 排 序 优 化 。 普 通 变 量 仅 仅 能 保 证 在 该 方 法 执 行 过 程 中 , 得 到正 确 结 果 , 但 是 不 保 证 程 序 代 码 的 执 行 顺 序 。
既 然 volatile 能 够 保 证 线 程 间 的 变 量 可 见 性 , 是 不 是 就 意 味 着 基 于 volatile 变 量 的 运 算 就 是 并 发 安 全 的 ?
显 然 不 是 的 。 基 于 volatile 变 量 的 运 算 在 并 发 下 不 一 定 是 安 全 的 。
volatile 变 量 在 各 个 线 程 的 工 作 内 存 , 不 存 在 一 致 性 问 题 ( 各 个 线 程 的 工 作 内 存 中 volatile 变 量 , 每 次 使 用 前 都 要 刷 新 到 主 内 存 ) 。
但 是 Java 里 面 的 运 算 并 非 原 子 操 作 , 导 致 volatile 变 量 的 运 算 在 并 发 下 一 样 是 不 安 全 的 。
请 对 比 下 volatile 对 比 Synchronized 的 异 同
Synchronized 既 能 保 证 可 见 性 , 又 能 保 证 原 子 性 , 而 volatile 只 能 保 证 可 见 性 , 无 法 保 证 原 子 性 。
Thread Local 和 Synchonized 都 用 于 解 决 多 线 程 并 发 访 问 , 防 止 任 务 在 共 享 资 源 上 产 生 冲 突 。 但 是 Thread Local 与 Synchronized 有 本 质 的 区 别 。
Synchronized 用 于 实 现 同 步 机 制 , 是 利 用 锁 的 机 制 使 变 量 或 代 码 块 在 某 一 时 该 只 能 被 一 个 线 程 访 问 , 是 一 种 “ 以 时 间 换 空 间 ” 的 方 式 。 而 Thread Local 为 每 一 个 线 程 都 提 供 了 变 量 的 副 本 , 使 得 每 个 线 程 在 某 一 时 间 访 问 到 的 并 不 是 同 一 个 对 象 , 根 除 了 对 变 量 的 共 享 , 是 一 种
“ 以 空 间 换 时 间 ” 的 方 式 。
请 谈 谈 Thread Local 是 怎 么 解 决 并 发 安 全 的
Thread Local 这 是 Java 提 供 的 一 种 保 存 线 程 私 有 信 息 的 机 制 , 因 为 其 在 整 个 线 程 生 命 周 期 内 有 效 , 所 以 可 以 方 便 地 在 一 个 线 程 关 联 的 不 同 业 务 模 块 之 间 传 递 信 息 , 比 如 事 务 ID、 Cookie 等 上 下 文 相 关 信 息 。
Thread Local 为 每 一 个 线 程 维 护 变 量 的 副 本 , 把 共 享 数 据 的 可 见 范 围 限 制 在 同 一 个 线 程 之 内 , 其 实 现 原 理 是 , 在 Thread Local 类 中 有 一 个 Map, 用 于 存 储 每 一 个 线 程 的 变 量 的 副 本 。
很 多 人 都 说 要 慎 用 Thread Local, 谈 谈 你 的 理 解 , 使 用 Thread Local 需 要 注 意 些 什 么 ?
Thread Local 的 实 现 是 基 于 一 个 所 谓 的 Thread Local Map, 在 Thread Local Map 中 , 它 的 key 是 一 个 弱 引 用 。
通 常 弱 引 用 都 会 和 引 用 队 列 配 合 清 理 机 制 使 用 , 但 是 Thread Local 是 个 例 外 , 它 并 没 有 这 么 做 。
这 意 味 着 , 废 弃 项 目 的 回 收 依 赖 于 显 式 地 触 发 , 否 则 就 要 等 待 线 程 结束 , 进 而 回 收 相 应 Thread Local Map! 这 就 是 很 多 OOM 的 来 源 , 所 以 通 常 都 会 建 议 , 应 用 一 定 要 自 己 负 责 remove, 并 且 不 要 和 线 程 池 配 合 , 因 为 worker 线 程 往 往 是 不 会 退 出 的 。