Java 线程
Java 爱好者 第 2 页 http://www.javafan.net
第一章 关于本教程
本教程有什么内容?
本教程研究了线程的基础知识 — 线程是什么、线程为什么有用以及怎么开始编写使用线程的简单
程序。
我们还将研究更复杂的、使用线程的应用程序的基本构件 — 如何在线程之间交换数据、如何控制
线程以及线程如何互相通信。
我应该学习这个教程吗?
本教程适用于拥有丰富 Java 语言应用知识,但又没有多少多线程或并发性经验的 Java 程序员。
学习完本教程之后,您应该可以编写一个使用线程的简单程序。您还应该可以阅读并理解以简单方
法使用线程的程序。
关于作者
Brian Goetz 是 developerWorks Java 技术专区的一名定期专栏作家,而且他在过去的 15 年里一
直是专业软件开发人员。他是 Quiotix 的首席顾问,这是一家位于加利福尼亚州洛斯阿尔托斯市
(Los Altos)的软件开发和咨询公司。
在流行的业界出版物上可以看到 Brian 发表和即将发表的文章。
可以通过 brian@quiotix.com 联系 Brian。
Java 线程
Java 爱好者 第 3 页 http://www.javafan.net
第二章 线程基础
什么是线程?
几乎每种操作系统都支持进程的概念 —— 进程就是在某种程度上相互隔离的、独立运行的程序。
线程化是允许多个活动共存于一个进程中的工具。大多数现代的操作系统都支持线程,而且线程的
概念以各种形式已存在了好多年。Java 是第一个在语言本身中显式地包含线程的主流编程语言,它
没有把线程化看作是底层操作系统的工具。
有时候,线程也称作轻量级进程。就象进程一样,线程在程序中是独立的、并发的执行路径,每个
线程有它自己的堆栈、自己的程序计数器和自己的局部变量。但是,与分隔的进程相比,进程中的
线程之间的隔离程度要小。它们共享内存、文件句柄和其它每个进程应有的状态。
进程可以支持多个线程,它们看似同时执行,但互相之间并不同步。一个进程中的多个线程共享相
同的内存地址空间,这就意味着它们可以访问相同的变量和对象,而且它们从同一堆中分配对象。
尽管这让线程之间共享信息变得更容易,但您必须小心,确保它们不会妨碍同一进程里的其它线程。
Java 线程工具和 API 看似简单。但是,编写有效使用线程的复杂程序并不十分容易。因为有多个
线程共存在相同的内存空间中并共享相同的变量,所以您必须小心,确保您的线程不会互相干扰。
每个 Java 程序都使用线程
每个 Java 程序都至少有一个线程 — 主线程。当一个 Java 程序启动时,JVM 会创建主线程,并
在该线程中调用程序的 main() 方法。
JVM 还创建了其它线程,您通常都看不到它们 — 例如,与垃圾收集、对象终止和其它 JVM 内务处
理任务相关的线程。其它工具也创建线程,如 AWT(抽象窗口工具箱(Abstract Windowing Toolkit))
或 Swing UI 工具箱、servlet 容器、应用程序服务器和 RMI(远程方法调用(Remote Method
Invocation))。
为什么使用线程?
Java 线程
Java 爱好者 第 4 页 http://www.javafan.net
在 Java 程序中使用线程有许多原因。如 果 您使用 Swing、servlet、RMI 或 Enterprise JavaBeans
(EJB)技术,您也许没有意识到您已经在使用线程了。
使用线程的一些原因是它们可以帮助:
• 使 UI 响应更快
• 利用多处理器系统
• 简化建模
• 执行异步或后台处理
响应更快的 UI
事件驱动的 UI 工具箱(如 AWT 和 Swing)有一个事件线程,它处理 UI 事件,如 击键或鼠标点击。
AWT 和 Swing 程序把事件侦听器与 UI 对象连接。当特定事件(如单击了某个按钮)发生时,这些
侦听器会得到通知。事件侦听器是在 AWT 事件线程中调用的。
如果事件侦听器要执行持续很久的任务,如检查一个大文档中的拼写,事件线程将忙于运行拼写检
查器,所以在完成事件侦听器之前,就不能处理额外的 UI 事件。这就会使程序看来似乎停滞了,
让用户不知所措。
要避免使 UI 延迟响应,事件侦听器应该把较长的任务放到另一个线程中,这样 AWT 线程在任务的
执行过程中就可以继续处理 UI 事件(包括取消正在执行的长时间运行任务的请求)。
利用多处理器系统
多处理器(MP)系统比过去更普及了。以前只能在大型数据中心和科学计算设施中才能找到它们。
现在许多低端服务器系统 — 甚至是一些台式机系统 — 都有多个处理器。
现代操作系统,包括 Linux、Solaris 和 Windows NT/2000,都可以利用多个处理器并调度线程在
任何可用的处理器上执行。
Java 线程
Java 爱好者 第 5 页 http://www.javafan.net
调度的基本单位通常是线程;如果某个程序只有一个活动的线程,它一次只能在一个处理器上运行。
如果某个程序有多个活动线程,那么可以同时调度多个线程。在精心设计的程序中,使用多个线程
可以提高程序吞吐量和性能。
简化建模
在某些情况下,使用线程可以使程序编写和维护起来更简单。考虑一个仿真应用程序,您要在其中
模拟多个实体之间的交互作用。给每个实体一个自己的线程可以使许多仿真和对应用程序的建模大
大简化。
另一个适合使用单独线程来简化程序的示例是在一个应用程序有多个独立的事件驱动的组件的时
候。例如,一个应用程序可能有这样一个组件,该组件在某个事件之后用秒数倒计时,并更新屏幕
显示。与其让一个主循环定期检查时间并更新显示,不如让一个线程什么也不做,一直休眠,直到
某一段时间后,更新屏幕上的计数器,这样更简单,而且不容易出错。这样,主线程就根本无需担
心计时器。
异步或后台处理
服务器应用程序从远程来源(如套接字)获取输入。当读取套接字时,如果当前没有可用数据,那
么对 SocketInputStream.read() 的调用将会阻塞,直到有可用数据为止。
如果单线程程序要读取套接字,而套接字另一端的实体并未发送任何数据,那么该程序只会永远等
待,而不执行其它处理。相反,程序可以轮询套接字,查看是否有可用数据,但通常不会使用这种
做法,因为会影响性能。
但是,如果您创建了一个线程来读取套接字,那么当这个线程等待套接字中的输入时,主线程就可
以执行其它任务。您甚至可以创建多个线程,这样就可以同时读取多个套接字。这样,当有可用数
据时,您会迅速得到通知(因为正在等待的线程被唤醒),而不必经常轮询以检查是否有可用数据。
使用线程等待套接字的代码也比轮询更简单、更不易出错。