剖析Java中线程编程的概念
作者:goldensun 时间:2022-02-02 04:12:51
Java线程的概念
和其他多数计算机语言不同,Java内置支持多线程编程(multithreaded programming)。
多线程程序包含两条或两条以上并发运行的部分。程序中每个这样的部分都叫一个线程(thread),每个线程都有独立的执行路径。因此,多线程是多任务处理的一种特殊形式。
你一定知道多任务处理,因为它实际上被所有的现代操作系统所支持。然而,多任务处理有两种截然不同的类型:基于进程的和基于线程的。认识两者的不同是十分重要的。
对很多读者,基于进程的多任务处理是更熟悉的形式。进程(process)本质上是一个执行的程序。因此,基于进程(process-based) 的多任务处理的特点是允许你的计算机同时运行两个或更多的程序。举例来说,基于进程的多任务处理使你在运用文本编辑器的时候可以同时运行Java编译器。在基于进程的多任务处理中,程序是调度程序所分派的最小代码单位。
在基于线程(thread-based) 的多任务处理环境中,线程是最小的执行单位。这意味着一个程序可以同时执行两个或者多个任务的功能。例如,一个文本编辑器可以在打印的同时格式化文本。所以,多进程程序处理“大图片”,而多线程程序处理细节问题。
多线程程序比多进程程序需要更少的管理费用。进程是重量级的任务,需要分配它们自己独立的地址空间。进程间通信是昂贵和受限的。进程间的转换也是很需要花费的。另一方面,线程是轻量级的选手。它们共享相同的地址空间并且共同分享同一个进程。线程间通信是便宜的,线程间的转换也是低成本的。当Java程序使用多进程任务处理环境时,多进程程序不受Java的控制,而多线程则受Java控制。
多线程帮助你写出CPU最大利用率的高效程序,因为空闲时间保持最低。这对Java运行的交互式的网络互连环境是至关重要的,因为空闲时间是公共的。举个例子来说,网络的数据传输速率远低于计算机处理能力,本地文件系统资源的读写速度远低于CPU的处理能力,当然,用户输入也比计算机慢很多。在传统的单线程环境中,你的程序必须等待每一个这样的任务完成以后才能执行下一步——尽管CPU有很多空闲时间。多线程使你能够获得并充分利用这些空闲时间。
Java线程模型
Java运行系统在很多方面依赖于线程,所有的类库设计都考虑到多线程。实际上,Java使用线程来使整个环境异步。这有利于通过防止CPU循环的浪费来减少无效部分。
为更好的理解多线程环境的优势可以将它与它的对照物相比较。单线程系统的处理途径是使用一种叫作轮询的事件循环方法。在该模型中,单线程控制在一无限循环中运行,轮询一个事件序列来决定下一步做什么。一旦轮询装置返回信号表明,已准备好读取网络文件,事件循环调度控制管理到适当的事件处理程序。直到事件处理程序返回,系统中没有其他事件发生。这就浪费了CPU时间。这导致了程序的一部分独占了系统,阻止了其他事件的执行。总的来说,单线程环境,当一个线程因为等待资源时阻塞(block,挂起执行),整个程序停止运行。
Java多线程的优点在于取消了主循环/轮询机制。一个线程可以暂停而不影响程序的其他部分。例如,当一个线程从网络读取数据或等待用户输入时产生的空闲时间可以被利用到其他地方。多线程允许活的循环在每一帧间隙中沉睡一秒而不暂停整个系统。在Java程序中出现线程阻塞,仅有一个线程暂停,其他线程继续运行。
线程存在于好几种状态。线程可以正在运行(running)。只要获得CPU时间它就可以运行。运行的线程可以被挂起(suspend),并临时中断它的执行。一个挂起的线程可以被恢复(resume,允许它从停止的地方继续运行。一个线程可以在等待资源时被阻塞(block)。
在任何时候,线程可以终止(terminate),这立即中断了它的运行。一旦终止,线程不能被恢复。
线程优先级
Java给每个线程安排优先级以决定与其他线程比较时该如何对待该线程。线程优先级是详细说明线程间优先关系的整数。作为绝对值,优先级是毫无意义的;当只有一个线程时,优先级高的线程并不比优先权低的线程运行的快。相反,线程的优先级是用来决定何时从一个运行的线程切换到另一个。这叫“上下文转换”(context switch)。决定上下文转换发生的规则很简单:
线程可以自动放弃控制。在I/O未决定的情况下,睡眠或阻塞由明确的让步来完成。在这种假定下,所有其他的线程被检测,准备运行的最高优先级线程被授予CPU。
线程可以被高优先级的线程抢占。在这种情况下,低优先级线程不主动放弃,处理器只是被先占——无论它正在干什么——处理器被高优先级的线程占据。基本上,一旦高优先级线程要运行,它就执行。这叫做有优先权的多任务处理。
当两个相同优先级的线程竞争CPU周期时,情形有一点复杂。对于Windows98这样的操作系统,等优先级的线程是在循环模式下自动划分时间的。对于其他操作系统,例如Solaris 2.x,等优先级线程相对于它们的对等体自动放弃。如果不这样,其他的线程就不会运行。
警告:不同的操作系统下等优先级线程的上下文转换可能会产生错误。
同步性
因为多线程在你的程序中引入了一个异步行为,所以在你需要的时候必须有加强同步性的方法。举例来说,如果你希望两个线程相互通信并共享一个复杂的数据结构,例如链表序列,你需要某些方法来确保它们没有相互冲突。也就是说,你必须防止一个线程写入数据而另一个线程正在读取链表中的数据。为此目的,Java在进程间同步性的老模式基础上实行了另一种方法:管程(monitor)。管程是一种由C.A.R.Hoare首先定义的控制机制。
你可以把管程想象成一个仅控制一个线程的小盒子。一旦线程进入管程,所有线程必须等待直到该线程退出了管程。用这种方法,管程可以用来防止共享的资源被多个线程操纵。
很多多线程系统把管程作为程序必须明确的引用和操作的对象。Java提供一个清晰的解决方案。没有“Monitor”类;相反,每个对象都拥有自己的隐式管程,当对象的同步方法被调用时管程自动载入。一旦一个线程包含在一个同步方法中,没有其他线程可以调用相同对象的同步方法。这就使你可以编写非常清晰和简洁的多线程代码,因为同步支持是语言内置的。
消息传递
在你把程序分成若干线程后,你就要定义各线程之间的联系。用大多数其他语言规划时,你必须依赖于操作系统来确立线程间通信。这样当然增加花费。然而,Java提供了多线程间谈话清洁的、低成本的途径——通过调用所有对象都有的预先确定的方法。Java的消息传递系统允许一个线程进入一个对象的一个同步方法,然后在那里等待,直到其他线程明确通知它出来。
Thread 类和Runnable 接口
Java的多线程系统建立于Thread类,它的方法,它的共伴接口Runnable基础上。Thread类封装了线程的执行。既然你不能直接引用运行着的线程的状态,你要通过它的代理处理它,于是Thread 实例产生了。为创建一个新的线程,你的程序必须扩展Thread 或实现Runnable接口。
Thread类定义了好几种方法来帮助管理线程。本章用到的方法如表所示: