Java多线程1.docx
- 文档编号:6229687
- 上传时间:2023-01-04
- 格式:DOCX
- 页数:37
- 大小:34.80KB
Java多线程1.docx
《Java多线程1.docx》由会员分享,可在线阅读,更多相关《Java多线程1.docx(37页珍藏版)》请在冰豆网上搜索。
Java多线程1
第11章多线程编程
和其他多数计算机语言不同,Java内置支持多线程编程(multithreadedprogramming)。
多线程程序包含两条或两条以上并发运行的部分。
程序中每个这样的部分都叫一个线程(thread),每个线程都有独立的执行路径。
因此,多线程是多任务处理的一种特殊形式。
你一定知道多任务处理,因为它实际上被所有的现代操作系统所支持。
然而,多任务处理有两种截然不同的类型:
基于进程的和基于线程的。
认识两者的不同是十分重要的。
对很多读者,基于进程的多任务处理是更熟悉的形式。
进程(process)本质上是一个执行的程序。
因此,基于进程(process-based)的多任务处理的特点是允许你的计算机同时运行两个或更多的程序。
举例来说,基于进程的多任务处理使你在运用文本编辑器的时候可以同时运行Java编译器。
在基于进程的多任务处理中,程序是调度程序所分派的最小代码单位。
在基于线程(thread-based)的多任务处理环境中,线程是最小的执行单位。
这意味着一个程序可以同时执行两个或者多个任务的功能。
例如,一个文本编辑器可以在打印的同时格式化文本。
所以,多进程程序处理“大图片”,而多线程程序处理细节问题。
多线程程序比多进程程序需要更少的管理费用。
进程是重量级的任务,需要分配它们自己独立的地址空间。
进程间通信是昂贵和受限的。
进程间的转换也是很需要花费的。
另一方面,线程是轻量级的选手。
它们共享相同的地址空间并且共同分享同一个进程。
线程间通信是便宜的,线程间的转换也是低成本的。
当Java程序使用多进程任务处理环境时,多进程程序不受Java的控制,而多线程则受Java控制。
多线程帮助你写出CPU最大利用率的高效程序,因为空闲时间保持最低。
这对Java运行的交互式的网络互连环境是至关重要的,因为空闲时间是公共的。
举个例子来说,网络的数据传输速率远低于计算机处理能力,本地文件系统资源的读写速度远低于CPU的处理能力,当然,用户输入也比计算机慢很多。
在传统的单线程环境中,你的程序必须等待每一个这样的任务完成以后才能执行下一步——尽管CPU有很多空闲时间。
多线程使你能够获得并充分利用这些空闲时间。
如果你在Windows98或Windows2000这样的操作系统下有编程经验,那么你已经熟悉了多线程。
然而,Java管理线程使多线程处理尤其方便,因为很多细节对你来说是易于处理的。
11.1Java线程模型
Java运行系统在很多方面依赖于线程,所有的类库设计都考虑到多线程。
实际上,Java使用线程来使整个环境异步。
这有利于通过防止CPU循环的浪费来减少无效部分。
为更好的理解多线程环境的优势可以将它与它的对照物相比较。
单线程系统的处理途径是使用一种叫作轮询的事件循环方法。
在该模型中,单线程控制在一无限循环中运行,轮询一个事件序列来决定下一步做什么。
一旦轮询装置返回信号表明,已准备好读取网络文件,事件循环调度控制管理到适当的事件处理程序。
直到事件处理程序返回,系统中没有其他事件发生。
这就浪费了CPU时间。
这导致了程序的一部分独占了系统,阻止了其他事件的执行。
总的来说,单线程环境,当一个线程因为等待资源时阻塞(block,挂起执行),整个程序停止运行。
Java多线程的优点在于取消了主循环/轮询机制。
一个线程可以暂停而不影响程序的其他部分。
例如,当一个线程从网络读取数据或等待用户输入时产生的空闲时间可以被利用到其他地方。
多线程允许活的循环在每一帧间隙中沉睡一秒而不暂停整个系统。
在Java程序中出现线程阻塞,仅有一个线程暂停,其他线程继续运行。
线程存在于好几种状态。
线程可以正在运行(running)。
只要获得CPU时间它就可以运行。
运行的线程可以被挂起(suspend),并临时中断它的执行。
一个挂起的线程可以被恢复(resume),允许它从停止的地方继续运行。
一个线程可以在等待资源时被阻塞(block)。
在任何时候,线程可以终止(terminate),这立即中断了它的运行。
一旦终止,线程不能被恢复。
11.1.1线程优先级
Java给每个线程安排优先级以决定与其他线程比较时该如何对待该线程。
线程优先级是详细说明线程间优先关系的整数。
作为绝对值,优先级是毫无意义的;当只有一个线程时,优先级高的线程并不比优先权低的线程运行的快。
相反,线程的优先级是用来决定何时从一个运行的线程切换到另一个。
这叫“上下文转换”(contextswitch)。
决定上下文转换发生的规则很简单:
·线程可以自动放弃控制。
在I/O未决定的情况下,睡眠或阻塞由明确的让步来完成。
在这种假定下,所有其他的线程被检测,准备运行的最高优先级线程被授予CPU。
·线程可以被高优先级的线程抢占。
在这种情况下,低优先级线程不主动放弃,处理器只是被先占——无论它正在干什么——处理器被高优先级的线程占据。
基本上,一旦高优先级线程要运行,它就执行。
这叫做有优先权的多任务处理。
当两个相同优先级的线程竞争CPU周期时,情形有一点复杂。
对于Windows98这样的操作系统,等优先级的线程是在循环模式下自动划分时间的。
对于其他操作系统,例如Solaris2.x,等优先级线程相对于它们的对等体自动放弃。
如果不这样,其他的线程就不会运行。
警告:
不同的操作系统下等优先级线程的上下文转换可能会产生错误。
11.1.2同步性
因为多线程在你的程序中引入了一个异步行为,所以在你需要的时候必须有加强同步性的方法。
举例来说,如果你希望两个线程相互通信并共享一个复杂的数据结构,例如链表序列,你需要某些方法来确保它们没有相互冲突。
也就是说,你必须防止一个线程写入数据而另一个线程正在读取链表中的数据。
为此目的,Java在进程间同步性的老模式基础上实行了另一种方法:
管程(monitor)。
管程是一种由C.A.R.Hoare首先定义的控制机制。
你可以把管程想象成一个仅控制一个线程的小盒子。
一旦线程进入管程,所有线程必须等待直到该线程退出了管程。
用这种方法,管程可以用来防止共享的资源被多个线程操纵。
很多多线程系统把管程作为程序必须明确的引用和操作的对象。
Java提供一个清晰的解决方案。
没有“Monitor”类;相反,每个对象都拥有自己的隐式管程,当对象的同步方法被调用时管程自动载入。
一旦一个线程包含在一个同步方法中,没有其他线程可以调用相同对象的同步方法。
这就使你可以编写非常清晰和简洁的多线程代码,因为同步支持是语言内置的。
11.1.3消息传递
在你把程序分成若干线程后,你就要定义各线程之间的联系。
用大多数其他语言规划时,你必须依赖于操作系统来确立线程间通信。
这样当然增加花费。
然而,Java提供了多线程间谈话清洁的、低成本的途径——通过调用所有对象都有的预先确定的方法。
Java的消息传递系统允许一个线程进入一个对象的一个同步方法,然后在那里等待,直到其他线程明确通知它出来。
11.1.4Thread类和Runnable接口
Java的多线程系统建立于Thread类,它的方法,它的共伴接口Runnable基础上。
Thread类封装了线程的执行。
既然你不能直接引用运行着的线程的状态,你要通过它的代理处理它,于是Thread实例产生了。
为创建一个新的线程,你的程序必须扩展Thread或实现Runnable接口。
Thread类定义了好几种方法来帮助管理线程。
本章用到的方法如表11-1所示:
表11-1管理线程的方法
方法
意义
getName
获得线程名称
getPriority
获得线程优先级
jsAlive
判定线程是否仍在运行
join
等待一个线程终止
run
线程的入口点.
sleep
在一段时间内挂起线程
start
通过调用运行方法来启动线程
到目前为止,本书所应用的例子都是用单线程的。
本章剩余部分解释如何用Thread和Runnable来创建、管理线程。
让我们从所有Java程序都有的线程:
主线程开始。
11.2主线程
当Java程序启动时,一个线程立刻运行,该线程通常叫做程序的主线程(mainthread),因为它是程序开始时就执行的。
主线程的重要性体现在两方面:
·它是产生其他子线程的线程
·通常它必须最后完成执行,因为它执行各种关闭动作。
尽管主线程在程序启动时自动创建,但它可以由一个Thread对象控制。
为此,你必须调用方法currentThread()获得它的一个引用,currentThread()是Thread类的公有的静态成员。
它的通常形式如下:
staticThreadcurrentThread( )
该方法返回一个调用它的线程的引用。
一旦你获得主线程的引用,你就可以像控制其他线程那样控制主线程。
让我们从复习下面例题开始:
//ControllingthemainThread.
classCurrentThreadDemo{
publicstaticvoidmain(Stringargs[]){
Threadt=Thread.currentThread();
System.out.println("Currentthread:
"+t);
//changethenameofthethread
t.setName("MyThread");
System.out.println("Afternamechange:
"+t);
try{
for(intn=5;n>0;n--){
System.out.println(n);
Thread.sleep(1000);
}
}catch(InterruptedExceptione){
System.out.println("Mainthreadinterrupted");
}
}
}
在本程序中,当前线程(自然是主线程)的引用通过调用currentThread()获得,该引用保存在局部变量t中。
然后,程序显示了线程的信息。
接着程序调用setName()改变线程的内部名称。
线程信息又被显示。
然后,一个循环数从5开始递减,每数一次暂停一秒。
暂停是由sleep()方法来完成的。
Sleep()语句明确规定延迟时间是1毫秒。
注意循环外的try/catch块。
Thread类的sleep()方法可能引发一个InterruptedException异常。
这种情形会在其他线程想要打搅沉睡线程时发生。
本例只是打印了它是否被打断的消息。
在实际的程序中,你必须灵活处理此类问题。
下面是本程序的输出:
Currentthread:
Thread[main,5,main]
Afternamechange:
Thread[MyThread,5,main]
5
4
3
2
1
注意t作为语句println()中参数运用时输出的产生。
该显示顺序:
线程名称,优先级以及组的名称。
默认情况下,主线程的名称是main。
它的优先级是5,这也是默认值,main也是所属线程组的名称。
一个线程组(threadgroup)是一种将线程作为一个整体集合的状态控制的数据结构。
这个过程由专有的运行时环境来处理,在此就不赘述了。
线程名改变后,t又被输出。
这次,显示了新的线程名。
让我们更仔细的研究程序中Thread类定义的方法。
sleep()方法按照毫秒级的时间指示使线程从被调用到挂起。
它的通常形式如下:
staticvoidsleep(longmilliseconds)throwsInterruptedException
挂起的时间被明确定义为毫秒。
该方法可能引发InterruptedException异常。
sleep()方法还有第二种形式,显示如下,该方法允许你指定时间是以毫秒还是以纳秒为周期。
staticvoidsleep(longmilliseconds,intnanoseconds)throws
InterruptedException
第二种形式仅当允许以纳秒为时间周期时可用。
如上述程序所示,你可以用setName()设置线程名称,用getName()来获得线程名称(该过程在程序中没有体现)。
这些方法都是Thread类的成员,声明如下:
finalvoidsetName(StringthreadName)
finalStringgetName( )
这里,threadName特指线程名称。
11.3创建线程
大多数情况,通过实例化一个Thread对象来创建一个线程。
Java定义了两种方式:
·实现Runnable接口。
·可以继承Thread类。
下面的两小节依次介绍了每一种方式。
11.3.1实现Runnable接口
创建线程的最简单的方法就是创建一个实现Runnable接口的类。
Runnable抽象了一个执行代码单元。
你可以通过实现Runnable接口的方法创建每一个对象的线程。
为实现Runnable接口,一个类仅需实现一个run()的简单方法,该方法声明如下:
publicvoidrun( )
在run()中可以定义代码来构建新的线程。
理解下面内容是至关重要的:
run()方法能够像主线程那样调用其他方法,引用其他类,声明变量。
仅有的不同是run()在程序中确立另一个并发的线程执行入口。
当run()返回时,该线程结束。
在你已经创建了实现Runnable接口的类以后,你要在类内部实例化一个Thread类的对象。
Thread类定义了好几种构造函数。
我们会用到的如下:
Thread(RunnablethreadOb,StringthreadName)
该构造函数中,threadOb是一个实现Runnable接口类的实例。
这定义了线程执行的起点。
新线程的名称由threadName定义。
建立新的线程后,它并不运行直到调用了它的start()方法,该方法在Thread类中定义。
本质上,start()执行的是一个对run()的调用。
Start()方法声明如下:
voidstart( )
下面的例子是创建一个新的线程并启动它运行:
//Createasecondthread.
classNewThreadimplementsRunnable{
Threadt;
NewThread(){
//Createanew,secondthread
t=newThread(this,"DemoThread");
System.out.println("Childthread:
"+t);
t.start();//Startthethread
}
//Thisistheentrypointforthesecondthread.
publicvoidrun(){
try{
for(inti=5;i>0;i--){
System.out.println("ChildThread:
"+i);
Thread.sleep(500);
}
}catch(InterruptedExceptione){
System.out.println("Childinterrupted.");
}
System.out.println("Exitingchildthread.");
}
}
classThreadDemo{
publicstaticvoidmain(Stringargs[]){
newNewThread();//createanewthread
try{
for(inti=5;i>0;i--){
System.out.println("MainThread:
"+i);
Thread.sleep(1000);
}
}catch(InterruptedExceptione){
System.out.println("Mainthreadinterrupted.");
}
System.out.println("Mainthreadexiting.");
}
}
在NewThread构造函数中,新的Thread对象由下面的语句创建:
:
t=newThread(this,"DemoThread");
通过前面的语句this表明在this对象中你想要新的线程调用run()方法。
然后,start()被调用,以run()方法为开始启动了线程的执行。
这使子线程for循环开始执行。
调用start()之后,NewThread的构造函数返回到main()。
当主线程被恢复,它到达for循环。
两个线程继续运行,共享CPU,直到它们的循环结束。
该程序的输出如下:
Childthread:
Thread[DemoThread,5,main]
MainThread:
5
ChildThread:
5
ChildThread:
4
MainThread:
4
ChildThread:
3
ChildThread:
2
MainThread:
3
ChildThread:
1
Exitingchildthread.
MainThread:
2
MainThread:
1
Mainthreadexiting.
如前面提到的,在多线程程序中,通常主线程必须是结束运行的最后一个线程。
实际上,一些老的JVM,如果主线程先于子线程结束,Java的运行时间系统就可能“挂起”。
前述程序保证了主线程最后结束,因为主线程沉睡周期1000毫秒,而子线程仅为500毫秒。
这就使子线程在主线程结束之前先结束。
简而言之,你将看到等待线程结束的更好途径。
11.3.2扩展Thread
创建线程的另一个途径是创建一个新类来扩展Thread类,然后创建该类的实例。
当一个类继承Thread时,它必须重载run()方法,这个run()方法是新线程的入口。
它也必须调用start()方法去启动新线程执行。
下面用扩展thread类重写前面的程序:
//CreateasecondthreadbyextendingThread
classNewThreadextendsThread{
NewThread(){
//Createanew,secondthread
super("DemoThread");
System.out.println("Childthread:
"+this);
start();//Startthethread
}
//Thisistheentrypointforthesecondthread.
publicvoidrun(){
try{
for(inti=5;i>0;i--){
System.out.println("ChildThread:
"+i);
Thread.sleep(500);
}
}catch(InterruptedExceptione){
System.out.println("Childinterrupted.");
}
System.out.println("Exitingchildthread.");
}
}
classExtendThread{
publicstaticvoidmain(Stringargs[]){
newNewThread();//createanewthread
try{
for(inti=5;i>0;i--){
System.out.println("MainThread:
"+i);
Thread.sleep(1000);
}
}catch(InterruptedExceptione){
System.out.println("Mainthreadinterrupted.");
}
System.out.println("Mainthreadexiting.");
}
}
该程序生成和前述版本相同的输出。
子线程是由实例化NewThread对象生成的,该对象从Thread类派生。
注意NewThread中super()的调用。
该方法调用了下列形式的Thread构造函数:
publicThread(StringthreadName)
这里,threadName指定线程名称。
11.3.3选择合适方法
到这里,你一定会奇怪为什么Java有两种创建子线程的方法,哪一种更好呢。
所有的问题都归于一点。
Thread类定义了多种方法可以被派生类重载。
对于所有的方法,惟一的必须被重载的是run()方法。
这当然是实现Runnable接口所需的同样的方法。
很多Java程序员认为类仅在它们被加强或修改时应该被扩展。
因此,如果你不重载Thread的其他方法时,最好只实现Runnable接口。
这当然由你决定。
然而,在本章的其他部分,我们应用实现runnable接口的类来创建线程。
11.4创建多线程
到目前为止,我们仅用到两个线程:
主线程和一个子线程。
然而,你的程序可以创建所需的更多线程。
例如,下面的程序创建了三个子线程:
//Createmultiplethreads.
classNewThreadimplementsRunnable{
Stringname;//nameofthread
Threadt;
NewThread(Stringthreadname){
name=threadname;
t=newThread(this,name);
System.out.println("Newthread:
"+t);
t.start();//Startthethread
}
//Thisistheentrypointforthread.
publicvoidrun(){
try{
for(inti=5;i>0;i--){
System.out.println(name+":
"+i);
Thread.sleep(1000);
}
}catch(InterruptedExceptione){
System.out.println(name+"Interrupted");
}
System.out.println(name+"exiting.");
}
}
classMultiThreadDemo{
publicstaticvoidmain(Stringargs[]){
newNewThread("One");//startthreads
newNewThread("Two");
newNewThread("Three");
try{
//waitforotherthreadstoend
Thread.sleep(10000);
}catch(InterruptedExceptione){
System.out.println("Mainthr
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Java 多线程