Java 线 程 的 讨 论 与 应 用
龙 士 工
一、 为 什 么 要 研 究 和 使 用 线 程
一 般 来 说, 计 算 机 正 在 执 行 的 程 序 称 作 进 程(process), 进 程 有 不 同 的 地 址 空 间 并 且 是 在 同 一 系 统 上 运 行 的 不 同 程 序, 如 W O R D 和Excel, 进 程 间 的 通 讯 是 很 费 时 而 且 有 限 的。 上 下 文 切 换、 改 变 运 行 的 进 程 也 是 非 常 复 杂 的。 进 程 间 通 讯 复 杂, 可 能 需 要 管 道、 消 息 队 列、 共 享 内 存(sharedmemory) 或 信 号 处 理 来 保 证 进 程 间 的 通 讯。 尽 管 许 多 程 序 都 在 运 行, 但 一 次 只 能 与 一 个 程 序 打 交 道。
线 程(thread) 是 指 进 程 中 单 一 顺 序 的 控 制 流。 又 称 为 轻 量 级 进 程。 线 程 则 共 享 相 同 的 地 址 空 间 并 共 同 构 成 一 个 大 的 进 程。 线 程 间 的 通 讯 是 非 常 简 单 而 有 效 的, 上 下 文 切 换 非 常 快 并 且 是 整 个 大 程 序 的 一 部 分 切 换。 线 程 仅 是 过 程 调 用, 它 们 彼 此 独 立 执 行, 线 程 使 得 在 一 个 应 用 程 序 中, 程 序 的 编 写 更 加 自 由 和 丰 富。 线 程 的 兴 趣 在 于, 一 个 程 序 中 同 时 使 用 多 个 线 程 来 完 成 不 同 的 任 务。 因 此 如 果 很 好 地 利 用 线 程, 可 以 大 大 简 化 应 用 程 序 设 计。 多 线 程 可 以 增 进 程 序 的 交 互 性, 提 供 更 好 的 能 力 和 功 能、 更 好 的GUI 和 更 好 的 服 务 器 功 能。 给 二 个 例 子 说 明 如 下:
例 一: 利 用 多 线 程 并 行 机 制 可 以 很 好 地 解 决 交 互 式 网 络 程 序 中 的 许 多 问 题, 如: 大 量 的 网 络 文 件 资 源 的 读 写、 用 户 输 入 响 应、 动 画 显 示 等 问 题 不 需 要 C P U 的 多 少 时 间; 而 耗 时 的 复 杂 计 算 通 常 并 不 需 要 立 即 响 应, 所 以 无 需 将 C P U 全 给 它。 例 如, 从 一 个 慢 速 的 网 络 上 读 取 一 数 据 流 也 许 要 1 分 钟 时 间, 但 需 要 C P U 参 与 传 输 数 据 的 时 间 则 非 常 短; 响 应 用 户 的 输 入 如 击 键, 就 算 最 快 的 输 入 员, 1 秒 钟 击 键 1 0 次, 也 不 需 要 C P U 的 多 少 时 间。 动 画 程 序 比 较 耗 时, 一 幅 画 在 1 秒 内 要 重 绘 5 - 1 0 次, 但 C P U 在 大 部 分 时 间 仍 处 于 空 闲 状 态。 在 传 统 的 单 线 程 环 境 下 的 问 题 是 用 户 必 须 等 待 每 个 任 务 完 成 后 才 能 进 行 下 一 个 任 务。 即 使 C P U 大 部 分 时 间 空 闲, 也 只 能 按 步 就 班 地 工 作。 多 线 程 可 以 很 好 地 解 决 这 些 问 题 避 免 引 起 用 户 的 等 待。 如: 耗 时 的 复 杂 计 算 应 用 就 可 划 分 成 两 个 控 制 线 程: 一 个 处 理GUI 的 用 户 事 件, 另 一 个 进 行 后 台 计 算。
例 二: 如 并 发 服 务 器, 它 面 向 不 定 长 时 间 内 处 理 完 的 请 求, 对 每 个 请 求 由 服 务 器 的 线 程 处 理。 传 统 的 并 发 服 务 器 往 往 是 基 于 多 进 程 机 制 的, 每 个 客 户 一 个 进 程, 需 要 操 作 系 统 的 干 预, 进 程 的 数 目 受 操 作 系 统 的 限 制。 本 文 利 用Java 的 线 程 机 制 建 立 了 基 于 多 线 程 的 并 发 服 务 器。 生 成 和 管 理 他 们 是 相 当 简 单 的 操 作。 线 程 被 用 来 建 立 请 求 驱 动 的 服 务 程 序, 每 个 客 户 一 个 线 程, 多 个 线 程 可 以 并 发 执 行。 特 别 地 线 程 具 有 如 下 特 性(1) 线 程 共 享 父 进 程 的 所 有 程 序 和 数 据(2) 有 自 身 的 运 行 单 元(3) 有 它 自 己 的 私 有 存 储 和 执 行 环 境( 尤 其 是 处 理 器 寄 存 器), 使 得 服 务 器 进 程 不 随 客 户 数 的 增 加 而 线 性 增 加。 可 减 少 服 务 器 进 程 的 压 力, 降 低 开 销, 充 分 利 用CPU 的 资 源。 以 上 并 发 服 务 器 在 某 一 瞬 间 由 同 一 服 务 器 进 程 所 产 生 的 多 个 并 发 线 程 对 多 个 客 户 的 并 发 请 求 采 取 分 而 治 之 的 措 施, 从 而 解 决 了 并 发 请 求 的 问 题。 各 线 程 即 可 以 独 立 操 作, 又 可 以 协 同 作 业。 降 低 了 服 务 器 的 复 杂 度。
Java 是 基 于 操 作 系 统 级 的 多 线 程 环 境 之 上 设 计 的,Java 的 运 行 器 依 靠 多 线 程 来 执 行 任 务, 并 且 所 有 类 库 在 设 计 时 都 考 虑 到 多 线 程 机 制。
二、Java 线 程 的 结 构
Java 支 持 一 种“ 抢 占 式”(preemptive) 调 度 方 式。
线 程 从 产 生 到 消 失, 可 分5 个 状 态:
Newborn
线 程 在 己 被 创 建 但 未 执 行 这 段 时 间 内, 处 于 一 个 特 殊 的"Newborn" 状 态, 这 时, 线 程 对 象 己 被 分 配 内 存 空 间, 其 私 有 数 据 己 被 初 始 化, 但 该 线 程 还 未 被 调 度。 此 时 线 程 对 象 可 通 过start() 方 法 调 度, 或 者 利 用stop() 方 法 杀 死. 新 创 建 的 线 程 一 旦 被 调 度, 就 将 切 换 到"Runnable" 状 态。
Runnable
Runnable 意 即 线 程 的 就 绪 状 态, 表 示 线 程 正 等 待 处 理 器 资 源, 随 时 可 被 调 用 执 行。 处 于 就 绪 状 态 的 线 程 事 实 上 己 被 调 度, 也 就 是 说, 它 们 己 经 被 放 到 某 一 队 列 等 待 执 行。 处 于 就 绪 状 态 的 线 程 何 时 可 真 正 执 行, 取 决 于 线 程 优 先 级 以 及 队 列 的 当 前 状 况。 线 程 的 优 先 级 如 果 相 同, 将 遵 循" 先 来 先 服 务" 的 调 度 原 则。
线 程 依 据 自 身 优 先 级 进 入 等 待 队 列 的 相 应 位 置。 某 些 系 统 线 程 具 有 最 高 优 先 级, 这 些 最 高 优 先 级 线 程 一 旦 进 入 就 绪 状 态, 将 抢 占 当 前 正 在 执 行 的 线 程 的 处 理 器 资 源, 当 前 线 程 只 能 重 新 在 等 待 队 列 寻 找 自 己 的 位 置. 这 些 具 有 最 高 优 先 级 的 线 程 执 行 完 自 己 的 任 务 之 后, 将 睡 眠 一 段 时 间, 等 待 被 某 一 事 件 唤 醒. 一 旦 被 唤, 这 些 线 程 就 又 开 始 抢 占 处 理 器 资 源。 这 些 最 高 优 先 级 线 程 通 常 用 来 执 行 一 些 关 键 性 任 务, 如 屏 幕 显 示。
低 优 先 级 线 程 需 等 待 更 长 的 时 间 才 能 有 机 会 运 行。 由 于 系 统 本 身 无 法 中 止 高 优 先 级 线 程 的 执 行, 因 此, 如 果 你 的 程 序 中 用 到 了 优 先 级 较 高 的 线 程 对 象, 那 么 最 好 不 时 让 这 些 线 程 放 弃 对 处 理 器 资 源 的 控 制 权, 以 使 其 他 线 程 能 够 有 机 运 行。
Running
"Running"( 运 行) 状 态 表 明 线 程 正 在 运 行, 该 线 己 经 拥 有 了 对 处 理 器 的 控 制 权, 其 代 码 目 前 正 在 运 行。 这 个 线 程 将 一 直 运 行 直 到 运 行 完 毕, 除 非 运 行 过 程 的 控 制 权 被 一 优 先 级 更 高 的 线 程 强 占。
综 合 起 来, 线 程 在 如 下3 种 情 形 之 下 将 释 放 对 处 理 器 的 控 制 权:
1. 主 动 或 被 动 地 释 放 对 处 理 器 资 源 的 控 制 权。 这 时, 该 线 程 必 须 再 次 进 入 等 待 队 列, 等 待 其 他 优 先 级 高 或 相 等 线 程 执 行 完 毕。
2. 睡 眠 一 段 确 定 的 时 间, 不 进 入 等 待 队 列。 这 段 确 定 的 时 间 段 到 期 之 后, 重 新 开 始 运 行。
3. 等 待 某 一 事 件 唤 醒 自 己。
Blocked
一 个 线 程 如 果 处 于"Blocked"( 堵 塞) 状 态, 那 么 暂 时 这 个 线 程 将 无 法 进 入 就 绪 队 列。 处 于 堵 塞 状 态 的 线 程 通 常 必 须 由 某 些 事 件 才 能 唤 醒。 至 于 是 何 种 事 件, 则 取 决 于 堵 塞 发 生 的 原 因: 处 于 睡 眠 中 的 线 程 必 须 被 堵 塞 一 段 固 定 的 时 间; 被 挂 起、 或 处 于 消 息 等 待 状 态 的 线 程 则 必 须 由 一 外 来 事 件 唤 醒。
Dead
Dead 表 示 线 程 巳 退 出 运 行 状 态, 并 且 不 再 进 入 就 绪 队 列. 其 中 原 因 可 能 是 线 程 巳 执 行 完 毕( 正 常 结 束), 也 可 能 是 该 线 程 被 另 一 线 程 所 强 行 中 断(kill)。
三、 创 建 和 使 用 线 程 的 基 本 方 法
1. 线 程 的 产 生
在Java 语 言 中, 可 采 用 两 种 方 式 产 生 线 程: 一 是 实 现 一 个Runnable 界 面, 二 是 扩 充 一 个Thread 类.java.lang 中 定 义 了 一 个 直 接 从 根 类Object 中 派 生 的Thread 类. 所 有 以 这 个 类 派 生 的 子 类 或 间 接 子 类, 均 为 线 程。 在 这 种 方 式 中, 需 要 作 为 一 个 线 程 执 行 的 类 只 能 继 承、 扩 充 单 一 的 父 类。 下 面 的 例 子 通 过 扩 充Thread 类, 用 该 线 程 自 己 的 实 现 来 覆 盖Thread.run(), 产 生 一 个 新 类Counter。run () 方 法 是Counter 类 线 程 所 作 的 全 部 操 作.
import java.lang.*;
public class Counter extends Thread
{
public void run ()
{....}
}
实 现Runnable 界 面 是 最 常 用 的 产 生 线 程 的 方 法, 它 打 破 了 扩 充Thread 类 方 式 的 限 制。
Java 语 言 源 码 中,Runnable 界 面 只 包 含 了 一 个 抽 象 方 法, 其 定 义 如 下:
package java.lang.*;
public interface Runnable {
public abstract void run ();
}
所 有 实 现 了Runnable 界 面 的 类 的 对 象 都 可 以 以 线 程 方 式 执 行. 下 面 的 例 子 产 生 与 上 面 例 子 相 同 的 类. 可 以 看 到counter 类 中 使 用 了 一 个Thread 类 的 变 量.
import java.lang.*;
public class counter implements Runnable
{ Thread T;
public void run ()
{...}
}
2、 基 本 方 法
.public synchronized void start()
启 动 线 程 对 象, 调 用 其run() 方 法, 随 即 返 回。
.pubilc final void stop()
停 止 线 程 的 执 行。
.public final void resume()
唤 醒 被 挂 起 的 线 程。 只 在 调 用suspend() 之 后 有 效。
.public final void suspend()
挂 起 线 程 的 执 行。
.public static void yield()
暂 时 中 止 当 前 正 在 执 行 的 线 程 对 象 的 运 行。 若 存 在 其 他 线 程, 则 随 后 调 用 下 一 个 线 程。
.public static void sleep (long mills)throws InterruptedException
使 当 前 正 处 运 行 状 态 的 线 程 睡 眠mills 毫 秒。
.public final void wait() throws InterruptedException
使 线 程 进 入 等 待 状 态, 直 到 被 另 一 线 程 唤 醒
.public final void motify()
把 线 程 状 态 的 变 化 通 知 给 另 一 等 待 线 程。
四、 线 程 的 同 步
线 程 的 使 用, 主 要 在 于 一 个 进 程 中 多 个 线 程 的 协 同 工 作, 所 以 线 程 的 同 步 就 很 重 要。 线 程 的 同 步 用 于 线 程 共 享 数 据, 转 换 和 控 制 线 程 的 执 行, 保 证 内 存 的 一 致 性。
在 Java 中, 运 行 环 境 使 用 程 序( Monitor) 来 解 决 线 程 同 步 的 问 题。 管 程 是 一 种 并 发 同 步 机 制, 它 包 括 用 于 分 配 一 个 特 定 的 共 享 资 源 或 一 组 共 享 资 源 的 数 据 和 方 法.
Java 为 每 一 个 拥 有 synchronized 方 法 的 对 象 实 例 提 供 了 一 个 唯 一 的 管 程。 为 了 完 成 分 配 资 源 的 功 能, 线 程 必 须 调 用 管 程 入 口。 管 程 入 口 就 是synchronized 方 法 入 口。 当 调 用 同 步( synchronized) 方 法 时, 该 线 程 就 获 得 了 该 管 程。
管 程 边 界 上 实 行 严 格 的 互 斥, 在 同 一 时 刻, 只 允 许 一 个 线 程 进 入 管 程; 当 管 程 中 已 有 了 一 个 线 程 时, 其 它 希 望 进 入 管 程 的 线 程 必 须 等 待, 这 种 等 待 是 由 管 程 自 动 管 理 的。
如 果 调 用 管 程 入 口 的 线 程 发 现 资 源 已 被 分 配, 管 程 中 的 这 个 线 程 将 调 用 等 待 操 作wait()。 进 入wait() 后, 该 线 程 放 弃 占 用 管 程, 在 管 程 外 面 等 待, 以 便 其 它 线 程 进 入 管 程。
最 终, 占 用 资 源 的 线 程 将 调 用 一 个 管 程 的 入 口 把 资 源 归 还 给 系 统, 此 时, 该 线 程 需 调 用 一 个 通 知 操 作notify(), 通 知 系 统 允 许 其 中 一 个 等 待 的 线 程 获 得 管 程 并 得 到 资 源。 被 通 知 的 线 程 是 排 队 的, 从 而 避 免 无 限 拖 延。
在 Java.lang 中 提 供 了 用 来 编 写 管 程 的 两 个 方 法: notify() 和wait()。 此 外 还 有notifyAll(), 它 通 知 所 有 等 待 的 线 程, 使 它 们 竞 争 管 程, 结 果 是 其 中 一 个 获 得 管 程, 其 佘 返 回 等 待 状 态。
五、 线 程 的 控 制
线 程 的 控 制 分 为 停 止 线 程 和 启 动 线 程。
.public final void suspend()
挂 起 线 程 的 执 行。
.public final void resume()
唤 醒 被 挂 起 的 线 程。 使 一 个 暂 停 的 线 程 可 用 于 调 度。
因 为 线 程 的 调 度 为 抢 占 式 机 制, 也 可 使 用 线 程 的 优 先 级 来 对 线 程 进 行 控 制。
.public final void setPriority(int newPriority)
设 置 线 程 优 先 级。
.public final int getPriority()
获 取 并 返 回 线 程 的 优 先 级。
线 程 的 优 先 级 用 于 在 运 行 队 列 中 给 线 程 排 序,Java 提 供 的 抢 占 式 调 度, 使 得 高 级 别 的 线 程 先 运 行。
六、 线 程 的 应 用
在 实 际 应 用 中, 线 程 使 用 的 范 围 很 广, 可 用 于 控 制 实 时 数 据 处 理、 快 速 的 网 络 服 务, 还 有 更 快 的 图 象 绘 制 和 打 印, 以 及 数 据 库 中 的 数 据 的 取 回 和 处 理 等 等。 在Java 中 一 个 在 不 停 运 行 的 提 供 一 些 基 本 服 务 的 例 子 是 垃 圾 收 集 线 程, 垃 圾 收 集 线 程,。 该 线 程 由Java 虚 拟 机 提 供。 它 扫 描 程 序 中 不 再 被 访 问 的 变 量, 将 其 所 占 的 系 统 资 源 释 放 给 系 统。
……