红联Linux门户
Linux帮助

Java线程的讨论与应用

发布时间:2006-05-03 19:23:22来源:红联作者:天山老童
一、为什么要研究和使用线程
一般来说,计算机正在执行的程序称作进程(process),进程有不同的地址空间并且是在同一系统上运行的不同程序,如WORD和Excel,进程间的通讯是很费时而且有限的。上下文切换、改变运行的进程也是非常复杂的。进程间通讯复杂,可能需要管道、消息队列、共享内存 (sharedmemory)或信号处理来保证进程间的通讯。尽管许多程序都在运行,但一次只能与一个程序打交道。
线程(thread)是指进程中单一顺序的控制流。又称为轻量级进程。线程则共享相同的地址空间并共同构成一个大的进程。线程间的通讯是非常简单而有效的,上下文切换非常快并且是整个大程序的一部分切换。线程仅是过程调用,它们彼此独立执行,线程使得在一个应用程序中,程序的编写更加自由和丰富。线程的兴趣在于,一个程序中同时使用多个线程来完成不同的任务。因此如果很好地利用线程,可以大大简化应用程序设计。多线程可以增进程序的交互性,提供更好的能力和功能、更好的GUI和更好的服务器功能。给二个例子说明如下:

例一:利用多线程并行机制可以很好地解决交互式网络程序中的许多问题,如:大量的网络文件资源的读写、用户输入响应、动画显示等问题不需要CPU的多少时间;而耗时的复杂计算通常并不需要立即响应,所以无需将CPU全给它。例如,从一个慢速的网络上读取一数据流也许要1分钟时间,但需要CPU参与传输数据的时间则非常短;响应用户的输入如击键,就算最快的输入员,1秒钟击键10次,也不需要CPU的多少时间。动画程序比较耗时,一幅画在1秒内要重绘5-10次,但CPU在大部分时间仍处于空闲状态。在传统的单线程环境下的问题是用户必须等待每个任务完成后才能进行下一个任务。即使CPU大部分时间空闲,也只能按步就班地工作。多线程可以很好地解决这些问题避免引起用户的等待。如:耗时的复杂计算应用就可划分成两个控制线程:一个处理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(longmills)throws Inter rupted Exception

使当前正处运行状态的线程睡眠mills毫秒。

.public final void wait()throws Interrupted Exception

使线程进入等待状态,直到被另一线程唤醒

.public final void motify()

把线程状态的变化通知给另一等待线程。

四、线程的同步
线程的使用,主要在于一个进程中多个线程的协同工作,所以线程的同步就很重要。线程的同步用于线程共享数据,转换和控制线程的执行,保证内存的一致性。
在Java中,运行环境使用程序(Monitor)来解决线程同步的问题。管程是一种并发同步机制,它包括用于分配一个特定的共享资源或一组共享资源的数据和方法.

Java为每一个拥有synchronized方法的对象实例提供了一个唯一的管程。为了完成分配资源的功能,线程必须调用管程入口。管程入口就是synchronized方法入口。当调用同步(synchronized)方法时,该线程就获得了该管程。

管程边界上实行严格的互斥,在同一时刻,只允许一个线程进入管程;当管程中已有了一个线程时,其它希望进入管程的线程必须等待,这种等待是由管程自动管理的。

如果调用管程入口的线程发现资源已被分配,管程中的这个线程将调用等待操作wait()。进入wait()后,该线程放弃占用管程,在管程外面等待,以便其它线程进入管程。

最终,占用资源的线程将调用一个管程的入口把资源归还给系统,此时,该线程需调用一个通知操作notify(),通知系统允许其中一个等待的线程获得管程并得到资源。被通知的线程是排队的,从而避免无限拖延。

在Java.lang中提供了用来编写管程的两个方法:notify()和wait()。此外还有notifyAll(),它通知所有等待的线程,使它们竞争管程,结果是其中一个获得管程,其佘返回等待状态。

五、线程的控制
线程的控制分为停止线程和启动线程。
.publicfinalvoidsuspend()

挂起线程的执行。

.publicfinalvoidresume()

唤醒被挂起的线程。使一个暂停的线程可用于调度。

因为线程的调度为抢占式机制,也可使用线程的优先级来对线程进行控制。

.publicfinalvoidsetPriority(intnewPriority)

设置线程优先级。

.publicfinalintgetPriority()

获取并返回线程的优先级。

线程的优先级用于在运行队列中给线程排序,Java提供的抢占式调度,使得高级别的线程先运行。

六、线程的应用
在实际应用中,线程使用的范围很广,可用于控制实时数据处理、快速的网络服务,还有更快的图象绘制和打印,以及数据库中的数据的取回和处理等等。在Java中一个在不停运行的提供一些基本服务的例子是垃圾收集线程,垃圾收集线程,。该线程由Java虚拟机提供。它扫描程序中不再被访问的变量,将其所占的系统资源释放给系统。
文章评论

共有 0 条评论