《《Java语言程序设计实验指导》电子教案第08章.ppt》由会员分享,可在线阅读,更多相关《《Java语言程序设计实验指导》电子教案第08章.ppt(24页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、第第8章章 多线程编程多线程编程 支持多线程编程是支持多线程编程是JavaJava语言的又一大特色。语言的又一大特色。多线程是相对于进程或单线程而言的,它具有并多线程是相对于进程或单线程而言的,它具有并发性、执行效率高的特点。本章将对发性、执行效率高的特点。本章将对JavaJava中的多中的多线程编程作初步介绍。线程编程作初步介绍。8.1 8.1 多线程编程概述多线程编程概述8.2 8.2 线程的创建线程的创建8.3 8.3 线程的优先级线程的优先级8.4 8.4 线程同步线程同步8.5 8.5 线程间通信线程间通信8.6 8.6 线程的控制线程的控制Return8.1 8.1 多线程编程概述
2、多线程编程概述 本节介绍多线程编程的基础知识,包括多线程的基本节介绍多线程编程的基础知识,包括多线程的基本概念、本概念、JavaJava的线程模型(线程优先级、同步性、消息的线程模型(线程优先级、同步性、消息传递)等方面的内容。传递)等方面的内容。8.1.1 8.1.1 什么是多线程什么是多线程8.1.2 Java8.1.2 Java线程模型线程模型Return8.1.1 8.1.1 什么是多线程什么是多线程 同同 其其 他他 大大 多多 数数 编编 程程 语语 言言 不不 同同,JavaJava内内 置置 支支 持持 多多 线线 程程 编编 程程(multithreaded multithr
3、eaded programmingprogramming)。多多线线程程程程序序包包含含两两条条或或两两条条以以上上并并发发运运行行的的部部分分,把把程程序序中中每每个个这这样样的的部部分分都都叫叫作作一一个个线线程程(threadthread)。每每个个线线程程都都有有独独立立的的执执行行路路径径,因因此此多多线线程程是是多多任任务务处处理理的的一一种种特特殊殊形式。形式。读读者者可可能能知知道道多多任任务务处处理理,它它实实际际上上被被所所有有的的现现代代操操作作系系统统所所支支持持。然然而而,多多任任务务处处理理有有两两种种截截然然不不同同的的类类型型:基基于于进进程程的的和和基基于于线
4、线程程的的。搞搞清清楚楚两两者者的的区区别别是是很很重重要要的的。对对大大多多数数读读者者来来说说,基基于于进进程程的的多多任任务务处处理理是是更更熟熟悉悉的的形形式式。进进程程(processprocess)本本质质上上是是一一个个执执行行的的程程序序。因因此此基基于于进进程程的的多多任任务务处处理理的的特特点点是是允允许许你你的的计计算算机机同同时时运运行行两两个个或或更更多多的的程程序序。举举例例来来说说,基基于于进进程程的的多多任任务务处处理理使使你你在在运运用用文文本本编编辑辑器器的的时时候候可可以以同同时时运运行行JavaJava编编译译器器。在在基基于于进进程程的的多多任任务务处
5、处理理中中,程程序是调度程序所分派的最小代码单位。序是调度程序所分派的最小代码单位。而而在在基基于于线线程程(thread-basedthread-based)的的多多任任务务处处理理环环境境中中,线线程程是是最最小小的的执执行行单单位位。这这意意味味着着一一个个程程序序可可以以同同时时执执行行两两个个或或者者多多个个任任务务的的功功能能。例例如如,一一个个文文本本编编辑辑器器可可以以在在打打印印的的同同时时格格式式化化文文本本。所所以以,多多进程程序处理进程程序处理“大图片大图片”,而多线程程序处理细节问题。,而多线程程序处理细节问题。Return多线程程序比多进程程序需要更少的管理费用。进
6、程是重量级的任务,需要分配给它们独立的地址空间。进程间通信是昂贵和受限的。进程间的转换也是很需要花费的。另一方面,线程是轻量级的选手。它们共享相同的地址空间并且共同分享同一个进程。线程间通信是便宜的,线程间的转换也是低成本的。当Java程序使用多进程任务处理环境时,多进程程序不受Java的控制,而多线程则受Java控制。多线程可帮助你编写出CPU最大利用率的高效程序,使得空闲时间保持最低。这对Java运行的交互式的网络互连环境是至关重要的,因为空闲时间是公共的。例如,网络的数据传输速率远低于计算机处理能力,而本地文件系统资源的读写速度也远低于CPU的处理能力。当然,用户输入也比计算机慢很多。在
7、传统的单线程环境中,程序必须等待每一个这样的任务完成以后才能执行下一步尽管CPU有很多空闲时间。多线程使你能够获得并充分利用这些空闲时间。8.1.2 Java8.1.2 Java线程模型线程模型 Java Java运行系统在很多方面依赖于线程,所有的类库设计都运行系统在很多方面依赖于线程,所有的类库设计都考虑到多线程。实际上,考虑到多线程。实际上,JavaJava使用线程来使整个环境异步。这使用线程来使整个环境异步。这有利于通过防止有利于通过防止CPUCPU循环的浪费来减少无效部分。循环的浪费来减少无效部分。为为更更好好地地理理解解多多线线程程环环境境的的优优势势,我我们们可可以以将将它它与与
8、它它的的对对照照物物相相比比较较。单单线线程程系系统统的的处处理理途途径径是是使使用用一一种种叫叫作作轮轮询询的的事事件件循循环环方方法法。在在该该模模型型中中,单单线线程程控控制制在在一一无无限限循循环环中中运运行行,轮轮询询一一个个事事件件序序列列来来决决定定下下一一步步做做什什么么。一一旦旦轮轮询询装装置置返返回回信信号号表表明明已已准准备备好好读读取取网网络络文文件件,事事件件循循环环调调度度控控制制管管理理到到适适当当的的事事件件处处理理程程序序。直直到到事事件件处处理理程程序序返返回回,系系统统中中没没有有其其他他事事件件发发生生。这这就就浪浪费费了了CPUCPU时时间间。这这导导
9、致致了了程程序序的的一一部部分分独独占占了了系系统统,阻阻止止了了其其他他事事件件的的执执行行。总总的的来来说说,单单线线程程环环境境,当当一一个个线线程程因因为为等等待待资资源源时时阻阻塞塞(blockblock,挂挂起起执执行行),整整个个程程序序停停止运行。止运行。Java多线程的优点就在于取消了主循环/轮询机制。一个线程可以暂停而不影响程序的其他部分。例如,当一个线程从网络读取数据或等待用户输入时产生的空闲时间可以被利用到其他地方。多线程允许活的循环在每一帧间隙中沉睡一秒而不暂停整个系统。在Java程序中出现线程阻塞,仅有一个线程暂停,其他线程继续运行。线程存在多种状态。线程可以正在运
10、行(running),只要获得了CPU时间它就可以运行;运行的线程可以被挂起(suspend),并临时中断它的执行;一个挂起的线程可以被恢复(resume),允许它从停止的地方继续运行;一个线程可以在等待资源时被阻塞(block);在任何时候,线程可以被终止(terminate),这将立即中断运行。一旦终止,线程不能被恢复。线程的各状态间关系见教材P190页图8-1所示。下面简要介绍与下面简要介绍与下面简要介绍与下面简要介绍与JavaJavaJavaJava线程相关的几个概念线程相关的几个概念线程相关的几个概念线程相关的几个概念 JavaJava给给每每个个线线程程安安排排优优先先级级以以决决
11、定定与与其其他他线线程程比比较较时时该该如如何何对对待待该该线线程程。线线程程优优先先级级是是详详细细说说明明线线程程间间优优先先关关系系的的整整数数。作作为为绝绝对对值值,优优先先级级是是毫毫无无意意义义的的;当当只只有有一一个个线线程程时时,优优先先级级高高的的线线程程并并不不比比优优先先级级低低的的线线程程运运行行的的快快。相相反反,线线程程的的优优先先级级是是用用来来决决定定何何时时从从一一个个运运行行的的线线程程切切换换到到另另一一个个。这这叫叫“上上下下文文转转换换”(context context switchswitch)。决决定定上上下下文文转转换换发发生生的的规则很简单:规
12、则很简单:l l线线程程可可以以自自动动放放弃弃控控制制。在在I/OI/O未未决决定定的的情情况况下下,睡睡眠眠或或阻阻塞塞由由明明确确的的让让步步来来完完成成。在在这这种种假假定定下下,所所有有其其他他的的线线程程被被检检测测,准准备备运运行行的的最最高优先级线程被授予高优先级线程被授予CPUCPU。l l线线程程可可以以被被高高优优先先级级的的线线程程抢抢占占。在在这这种种情情况况下下,低低优优先先级级线线程程不不主主动动放放弃弃,处处理理器器只只是是被被先先占占无无论论它它正正在在干干什什么么处处理理器器被被高高优优先先级级的的线线程程占占据据。基基本本上上,一一旦旦高高优优先先级级线线
13、程程要要运运行行,它它就就执执行行。这这叫叫做做有有优先级的多任务处理。优先级的多任务处理。当两个相同优先级的线程竞争当两个相同优先级的线程竞争CPUCPU周期时,情形有一点复杂。对于周期时,情形有一点复杂。对于WindowsWindows这样的操作系统,等优先级的线程是在循环模式下自动划分时间的。这样的操作系统,等优先级的线程是在循环模式下自动划分时间的。对于其他一些非对于其他一些非WindowsWindows操作系统而,如操作系统而,如Solaris 2.xSolaris 2.x,等优先级线程相对,等优先级线程相对于它们的对等体自动放弃。如果不这样,其他的线程就不会运行。于它们的对等体自动
14、放弃。如果不这样,其他的线程就不会运行。1 1线程优先级线程优先级2 2 2 2同步性同步性同步性同步性 由由于于多多线线程程在在程程序序中中引引入入了了一一个个异异步步行行为为,故故在在需需要要的的时时候候必必须须有有加加强强同同步步性性的的方方法法。举举例例来来说说,如如果果你你希希望望两两个个线线程程相相互互通通信信并并共共享享一一个个复复杂杂的的数数据据结结构构,例例如如链链表表序序列列,就就需需要要某某些些方方法法来来确确保保它它们们没没有有相相互互冲冲突突。也也就就是是说说,你你必必须须防防止止一一个个线线程程写写入入数数据据而而另另一一个个线线程程正正在在读读取取链链表表中中的的
15、数数据据。为为此此,JavaJava在在进进程程间间同同步步性性的的老老模模式式基基础础上上实实 行行 了了 另另 外外 的的 一一 种种 方方 法法:管管 程程(monitormonitor)。管管 程程 是是 一一 种种 由由C.A.R.HoareC.A.R.Hoare首首先先定定义义的的控控制制机机制制。你你可可以以把把管管程程想想象象成成一一个个仅仅控控制制一一个个线线程程的的小小盒盒子子。一一旦旦线线程程进进入入管管程程,所所有有线线程程必必须须等等待待直直到到该该线线程程退退出出了了管管程程。用用这这种种方方法法,管管程程可可以以用用来来防防止止共共享享的的资资源源被被多多个个线线
16、程操纵。程操纵。很很多多多多线线程程系系统统将将管管程程作作为为程程序序必必须须明明确确的的引引用用和和操操作作的的对对象象。但但JavaJava提提供供一一个个清清晰晰的的解解决决方方案案,不不提提供供“Monitor”“Monitor”类类;相相反反,每每个个对对象象都都拥拥有有自自己己的的隐隐式式管管程程,当当对对象象的的同同步步方方法法被被调调用用时时管管程程自自动动载载入入。一一旦旦一一个个线线程程包包含含在在一一个个同同步步方方法法中中,没没有有其其他他线线程程可可以以调调用用相相同同对对象象的的同同步步方方法法。这这就就使使你你可可以以编编写写非非常常清清晰晰和和简简洁洁的的多多
17、线线程程代代码码,因为同步支持是语言内置的。因为同步支持是语言内置的。3 3 3 3消息传递消息传递消息传递消息传递 当当把把程程序序分分成成若若干干线线程程后后,就就要要定定义义各各线线程程之之间间的的联联系系。用用大大多多数数其其他他语语言言规规划划时时必必须须依依赖赖于于操操作作系系统统来来确确立立线线程程间间通通信信,这这样样当当然然要要增增加加花花费费。然然而而,JavaJava提提供供了了多多线线程程间间谈谈话话清清洁洁的的、低低成成本本的的途途径径通通过过调调用用所所有有对对象象都都有有的的预预先先确确定定的的方方法法。JavaJava的的消消息息传传递递系系统统允允许许一一个个
18、线线程程进进入入一一个个对对象象的的一一个个同同步步方方法法,然然后后在在那那里里等等待待,一一直直等等到其他线程明确通知它出来。到其他线程明确通知它出来。Return Java的多线程系统建立于Thread类、方法以及共伴接口Runnable基础上。Thread类封装了线程的执行。既然不能直接引用运行着的线程的状态,就要通过它的代理处理它。于是Thread 实例产生了。为创建一个新的线程,程序中必须扩展Thread 或实现Runnable接口。Thread类定义了好几种方法来帮助管理线程,见教材P192页表8-1中所列。4 4ThreadThread类和类和RunnableRunnable接
19、口接口8.2 8.2 线程的创建线程的创建 本节介绍在本节介绍在JavaJava中如何创建线程。主要内容包括主中如何创建线程。主要内容包括主线程、多线程的创建、相关方法的使用等。线程、多线程的创建、相关方法的使用等。8.2.1 8.2.1 关于主线程关于主线程8.2.2 8.2.2 创建一个线程创建一个线程8.2.3 8.2.3 创建多线程创建多线程8.2.4 8.2.4 使用使用isAlive()isAlive()和和join()join()Return8.2.1 8.2.1 关于主线程关于主线程 当当JavaJava程程序序启启动动时时,一一个个线线程程立立刻刻运运行行,该该线线程程通通常
20、常就就叫叫做做程程序序的的主主线线程程(main main threadthread),因为它是程序开始时就执行的。主线程的重要性主要体现在两方面:),因为它是程序开始时就执行的。主线程的重要性主要体现在两方面:l l它是产生其他子线程的线程;它是产生其他子线程的线程;l l通常它必须最后完成执行,因为它执行各种关闭动作。通常它必须最后完成执行,因为它执行各种关闭动作。尽尽管管主主线线程程在在程程序序启启动动时时自自动动创创建建,但但它它可可以以由由一一个个ThreadThread对对象象控控制制。为为此此,必必须须调调用用方方法法currentThread()currentThread()获
21、获得得它它的的一一个个引引用用,currentThread()currentThread()是是ThreadThread类类的的公公有的静态成员。它的一般形式如下有的静态成员。它的一般形式如下staticThreadcurrentThread()staticThreadcurrentThread()该该方方法法返返回回一一个个调调用用它它的的线线程程的的引引用用。一一旦旦获获得得主主线线程程的的引引用用,就就可可以以像像控控制制其其他线程那样控制主线程。他线程那样控制主线程。下面我们考察一下教材下面我们考察一下教材P192P192 193193页的程序代码。页的程序代码。在上面的程序中,当前线
22、程(当然是主线程)的引用通过调用在上面的程序中,当前线程(当然是主线程)的引用通过调用currentThread()currentThread()获得,该引用保存在局部变量获得,该引用保存在局部变量t t中。然后,程序显示了线程的信息。接着,程序调用中。然后,程序显示了线程的信息。接着,程序调用setNamesetName()()改变线程的内部名称,线程信息又被显示。然后,一个循环数从改变线程的内部名称,线程信息又被显示。然后,一个循环数从5 5开始递减,每数开始递减,每数一次暂停一秒。暂停是由一次暂停一秒。暂停是由sleep()sleep()方法来完成的,方法来完成的,sleep()slee
23、p()语句明确规定延迟时间是语句明确规定延迟时间是1 1毫秒。请读者注意循环外的毫秒。请读者注意循环外的try/catchtry/catch块。块。ThreadThread类的类的sleep()sleep()方法可能引发一个方法可能引发一个InterruptedExceptionInterruptedException异常,这种情形会在其他线程想要打搅沉睡线程时发生。本异常,这种情形会在其他线程想要打搅沉睡线程时发生。本例只是打印了它是否被打断的消息。在实际的程序中,必须灵活处理此类问题。例只是打印了它是否被打断的消息。在实际的程序中,必须灵活处理此类问题。Return8.2.2 8.2.2
24、创建一个线程创建一个线程大多数情况,通过实例化一个大多数情况,通过实例化一个ThreadThread对象来创建一个线程。对象来创建一个线程。JavaJava定义了两种方式:定义了两种方式:l l实现实现Runnable Runnable 接口;接口;l l以继承以继承ThreadThread类的方式。类的方式。创建线程最简单的方法就是创建一个实现Runnable 接口的类,Runnable抽象了一个执行代码单元。可以通过实现Runnable接口的方法创建每一个对象的线程。为实现 Runnable 接口,一个类仅需实现一个run()的简单方法,该方法声明如下:publicvoidrun()在ru
25、n()中,可以定义代码来构建新的线程。重要的是:run()方法能够像主线程那样调用其他方法,引用其他类,声明变量。仅有的不同是:run()在程序中确立另一个并发的线程执行入口。当run()返回时,该线程结束。在已经创建了实现Runnable接口的类以后,需要在类内部实例化一个Thread类的对象。Thread 类定义了好几种构造函数。我们会用到的如下:Thread(RunnablethreadOb,StringthreadName)在该构造函数中,threadOb是一个实现Runnable接口类的实例。这定义了线程执行的起点,新线程的名称由threadName定义。建立新的线程后,它并不运行直
26、到调用其start()方法,该方法在Thread 类中定义。从本质上讲,start()执行的是一个对run()的调用。start()方法声明如下:voidstart()下面我们分别对这两种方法进行介绍:1 1实现实现RunnableRunnable接口接口2 2 2 2扩展扩展扩展扩展ThreadThreadThreadThread 创创建建线线程程的的另另一一个个途途径径是是创创建建一一个个新新类类来来扩扩展展ThreadThread类类,然然后后再再创创建建该该类类的的实实例例。当当一一个个类类继继承承ThreadThread时时,它它必必须须重重载载run()run()方方法法,这这个个
27、run()run()方方法法是是新新线线程程的的入入口口。同同时时,它它也也必须调用必须调用start()start()方法去启动新线程执行。方法去启动新线程执行。Return 到这里,读者可能会奇怪为什么Java有两种创建子线程的方法,哪一种更好呢。所有的问题都归于一点。Thread类定义了多种方法可以被派生类重载。对于所有的方法,唯一的必须被重载的是run()方法。这当然是实现Runnable接口所需的同样的方法。很多Java程序员认为类仅在它们被加强或修改时被扩展。因此,如果你不重载Thread的其他方法,最好只实现Runnable 接口,这当然由自己决定。在本章的其他部分,我们应用实现
28、Runnable接口的类来创建线程。3 3选择合适的方法选择合适的方法8.2.3 8.2.3 创建多线程创建多线程 到目前为止,我们仅用到两个线程:到目前为止,我们仅用到两个线程:主线程和一个子线程。然而,我们完全主线程和一个子线程。然而,我们完全可以创建所需的更多线程。例如,教材可以创建所需的更多线程。例如,教材P197P197 198198页的程序创建了页的程序创建了3 3个子线程。个子线程。详细分析该程序。详细分析该程序。Return8.2.4 8.2.4 使用使用isAlive()isAlive()和和join()join()如如前前所所述述,我我们们一一般般是是希希望望主主线线程程最
29、最后后结结束束。在在上上面面的的例例子子中中,这这点点是是通通过过在在main()main()中中调调用用sleep()sleep()来来实实现现的的,经经过过足足够够长长时时间间的的延延迟迟以以确确保保所所有有子子线线程程都都先先于于主主线线程程结结束束。然然而而,这这并并不不是是一一个个好好的的解解决决方方法法。因因为为有有时时候候存存在在这这个个问问题题:一一个个线线程程如如何何知知道道另另一一线线程程已已经经结结束束?幸幸运运的的是是,ThreadThread类提供了解决此问题的有效方法。类提供了解决此问题的有效方法。有有两两种种方方法法可可以以判判定定一一个个线线程程是是否否结结束束
30、:第第一一,可可以以在在线线程程中中调调用用isAliveisAlive()()。这种方法由。这种方法由ThreadThread定义,它的一般形式如下定义,它的一般形式如下finalbooleanisAlive()finalbooleanisAlive()如如果果所所调调用用线线程程仍仍在在运运行行,isAlive()isAlive()方方法法返返回回truetrue,如如果果不不是是则则返返回回falsefalse。但但isAlive()isAlive()很很少少用用到到,等等待待线线程程结结束束的的更更常常用用的的方方法法是是调调用用join()join(),描描述如下述如下finalvo
31、idjoin()throwsInterruptedExceptionfinalvoidjoin()throwsInterruptedException 该方法等待所调用线程结束,该名字来自于要求线程等待直到指定线该方法等待所调用线程结束,该名字来自于要求线程等待直到指定线程参与的概念。程参与的概念。join()join()的附加形式允许给等待指定线程结束定义一个最大的附加形式允许给等待指定线程结束定义一个最大时间。详细分析教材时间。详细分析教材P199P199 200200页的程序。页的程序。Return8.3 8.3 线程的优先级线程的优先级 线线程程优优先先级级被被线线程程调调度度用用来来
32、判判定定何何时时某某个个线线程程允允许许运运行行。理理论论上上,优优先先级级高高的的线线程程比比优优先先级级低低的的线线程程获获得得更更多多的的CPUCPU时时间间。实实际际上上,线线程程获获得得的的CPUCPU时时间间通通常常由由包包括优先级在内的多个因素决定。一个优先级高的线程自然比优先级低的线程优先。括优先级在内的多个因素决定。一个优先级高的线程自然比优先级低的线程优先。理理论论上上,等等优优先先级级线线程程有有同同等等的的权权利利使使用用CPUCPU,但但你你必必须须小小心心。需需要要记记住住的的是是,JavaJava是是被被设设计计成成能能在在很很多多环环境境下下工工作作的的。不不同
33、同环环境境下下实实现现多多任任务务处处理理从从本本质质上上来来看看是是可可能能的的。为为安安全全起起见见,等等优优先先级级线线程程有有时时候候也也受受到到控控制制。这这保保证证了了所所有有线线程程在在无无优优先先级级的的操操作作系系统统下下都都有有机机会会运运行行。实实际际上上,在在无无优优先先级级的的环环境境下下,多多数数线线程程仍仍然然有有机机会会运运行行,因因为为很很多多线线程程不不可可避避免免地地会会遭遭遇遇阻阻塞塞,例例如如等等待待输输入入输输出出。遇遇到到这这种种情情形形,阻阻塞塞的的线线程程挂挂起起,其其他他线线程程运运行行。但但是是如如果果你你希希望望多多线线程程执执行行得得顺
34、顺利利的的话话,最最好好不不要要采采用用这这种种方方法法。同同样样,有有些些类类型型的的任任务务是是占占CPUCPU的的。对对于于这这些些支支配配CPUCPU类类型型的线程,有时你希望能够支配它们,以便使其他线程可以运行。的线程,有时你希望能够支配它们,以便使其他线程可以运行。设设置置线线程程的的优优先先级级,用用setPriority()setPriority()方方法法,该该方方法法也也是是ThreadThread的的成成员员。它它的的通常形式为通常形式为 finalvoidsetPriority(intlevel)finalvoidsetPriority(intlevel)这里,这里,l
35、evellevel指定了对所调用的线程的新的优先权的设置。指定了对所调用的线程的新的优先权的设置。LevelLevel的值必须在的值必须在MIN_PRIORITYMIN_PRIORITY到到MAX_PRIORITYMAX_PRIORITY范围内。通常,它们的值分别是范围内。通常,它们的值分别是1 1和和1010。要返回一个。要返回一个线程为默认的优先级,指定线程为默认的优先级,指定NORM_PRIORITYNORM_PRIORITY,通常值为,通常值为5 5。这些优先级在。这些优先级在ThreadThread中都中都被定义为被定义为finalfinal型变量。型变量。Return8.4 8.4
36、 线程同步线程同步 当两个或两个以上的线程需要共享资源,它们需要当两个或两个以上的线程需要共享资源,它们需要某种方法来确定资源在某一刻仅被一个线程占用,达到某种方法来确定资源在某一刻仅被一个线程占用,达到此目的的过程叫做同步(此目的的过程叫做同步(synchronizationsynchronization)。)。JavaJava为为此提供了独特的、很有效的支持机制。此提供了独特的、很有效的支持机制。8.4.1 8.4.1 使用同步方法使用同步方法8.4.2 8.4.2 同步语句同步语句Return8.4.1 8.4.1 8.4.1 8.4.1 使用同步方法使用同步方法使用同步方法使用同步方法
37、 同步的关键是管程(也叫信号量,即同步的关键是管程(也叫信号量,即semaphoresemaphore)的概念。管程是一个互斥独占锁定的对)的概念。管程是一个互斥独占锁定的对象,或称互斥体(象,或称互斥体(mutexmutex)。在给定的时间,仅有一)。在给定的时间,仅有一个线程可以获得管程。当一个线程需要锁定时,它必个线程可以获得管程。当一个线程需要锁定时,它必须进入管程。所有其他的试图进入已经锁定的管程的须进入管程。所有其他的试图进入已经锁定的管程的线程必须挂起直到第一个线程退出管程。这些其他的线程必须挂起直到第一个线程退出管程。这些其他的线程被称为等待管程。线程被称为等待管程。我我们们可
38、可以以用用两两种种方方法法同同步步化化代代码码。通通过过调调用用sleep()sleep(),callcall()()方方法法允允许许执执行行转转换换到到另另一一个个线线程程。两两者者都都包包括括synchronizedsynchronized关键字的运用。分析教材关键字的运用。分析教材P204P204页的示例。页的示例。Return8.4.2 8.4.2 8.4.2 8.4.2 同步语句同步语句同步语句同步语句 尽尽管管在在创创建建的的类类的的内内部部创创建建同同步步方方法法是是获获得得同同步步的的简简单单和和有有效效的的方方法法,但但它它并并非非在在任任何何时时候候都都有有效效。假假设设你
39、你想想获获得得不不为为多多线线程程访访问问设设计计的的类类对对象象的的同同步步访访问问,也也就就是是该该类类没没有有用用到到synchronizedsynchronized方方法法。而而且且,该该类类不不是是你你自自己己,而而是是第第三三方方创创建建的的,就就不不能能获获得得它它的的源源代代码码。这这样样,就就不不能能在在相相关关方方法法前前加加synchronizedsynchronized修修饰饰符符。怎怎样样才才能能使使该该类类的的一一个个对对象象同同步步化化呢呢?解解决决的的方方法法很很简简单单:只只需需将将对对这这个个类类定定义义的的方方法法的的调调用放入一个用放入一个synchro
40、nizedsynchronized块内就可以了。块内就可以了。下面是下面是synchronizedsynchronized语句的一般形式语句的一般形式synchronized(object)synchronized(object)/statementstobesynchronized/statementstobesynchronized 其中,其中,objectobject是对同步对象的引用。如果你想要同步的只是一个是对同步对象的引用。如果你想要同步的只是一个语句,那么不需要花括号。一个同步块确保对语句,那么不需要花括号。一个同步块确保对objectobject成员方法的调用成员方法的调用仅在
41、当前线程成功进入仅在当前线程成功进入objectobject管程后发生。管程后发生。Return8.5 8.5 线程间通信线程间通信 前面的例子无条件地阻塞了其他线程异步访问某前面的例子无条件地阻塞了其他线程异步访问某个方法。个方法。JavaJava对象中隐式管程的应用是很强大的,但对象中隐式管程的应用是很强大的,但是我们可以通过进程间通信达到更微妙的境界,这在是我们可以通过进程间通信达到更微妙的境界,这在JavaJava中是很简单的。中是很简单的。8.5.1 Java8.5.1 Java中的线程通讯中的线程通讯8.5.2 8.5.2 关于死锁关于死锁Return8.5.1 Java8.5.1
42、 Java中的线程通讯中的线程通讯 多多线线程程通通过过把把任任务务分分成成离离散散的的和和合合乎乎逻逻辑辑的的单单元元代代替替了了事事件件循循环环程程序序。线线程程还还有有另另外外一一个个优优点点:它它远远离离了了轮轮询询。轮轮询询通通常常由由重重复复监监测测条条件件的的循循环环实实现现。一一旦旦条条件件成成立,就要采取适当的行动。这浪费了立,就要采取适当的行动。这浪费了CPUCPU时间。时间。为为避避免免轮轮询询,JavaJava包包含含了了通通过过wait()wait(),notify()notify()和和notifyAll()notifyAll()方方法法实实现现的的一一个个进进程程
43、间间通通信信机机制制。这这些些方方法法在在对对象象中中是是用用finalfinal方方法法实实现现的的,所所以以所所有有的的类类都都含含有有它它们们。这这三三个个方方法法仅仅在在synchronizedsynchronized方方法法中中才才能能被被调调用用。尽尽管管这这些些方方法法从从计计算算机机科科学远景方向上来说具有概念的高度先进性,实际中用起来却是很简单的。学远景方向上来说具有概念的高度先进性,实际中用起来却是很简单的。wait()wait()wait()wait()告告知知被被调调用用的的线线程程放放弃弃管管程程进进入入睡睡眠眠直直到到其其他他线线程程进进入入相相同同管管程程并并且且
44、调用调用notify()notify()。notify()notify()notify()notify()恢复相同对象中第一个调用恢复相同对象中第一个调用wait()wait()的线程。的线程。notifyAllnotifyAllnotifyAllnotifyAll()()()()恢复相同对象中所有调用恢复相同对象中所有调用wait()wait()的线程。的线程。这些方法在这些方法在ObjectObject中被声明,如下所示中被声明,如下所示finalvoidwait()throwsfinalvoidwait()throwsInterruptedExceptionInterruptedExce
45、ptionfinalvoidnotify()finalvoidnotify()finalvoidfinalvoidnotifyAllnotifyAll()()wait()wait()存在的另外的形式允许你定义等待时间。分析教材存在的另外的形式允许你定义等待时间。分析教材P207P207 210210页的程序段。页的程序段。Return8.5.2 8.5.2 关于死锁关于死锁 需需要要避避免免的的与与多多任任务务处处理理有有关关的的特特殊殊错错误误类类型型是是死死锁锁(deadlockdeadlock)。死死锁锁发发生生在在当当两两个个线线程程对对一一对对同同步步对对象象有有循循环环依依赖赖关关
46、系系时时。例例如如,假假定定一一个个线线程程进进入入了了对对象象X X的的管管程程而而另另一一个个线线程程进进入入了了对对象象Y Y的的管管程程。如如果果X X的的线线程程试试图图调调用用Y Y的的同同步步方方法法,它它将将像像预预料料的的一一样样被被锁锁定定。而而Y Y的的线线程程同同样样希希望望调调用用X X的的一一些些同同步步方方法法,线线程程永永远远等等待待,因因为为为为到到达达X X,必必须须释释放放自自己己的的Y Y的的锁锁定定以以使使第第一一个个线线程程可可以以完完成成。死死锁锁是是很很难难调调试试的的错错误误,这这是是因因为为:第第一一,通通常常它它极极少少发发生生,只只有有到
47、到两两线线程程的的时时间间段段刚刚好好符符合合时时才才能能发发生生;第第二二,它它可可能能包包含含多多于于两两个个的的线线程程和和同同步步对对象象。也也就就是是说说,死死锁锁在在比比刚刚讲讲述述的的例例子子有更多复杂的事件序列的时候可以发生。有更多复杂的事件序列的时候可以发生。为充分理解死锁,观察它的行为是很有用的。教材为充分理解死锁,观察它的行为是很有用的。教材P211P211 212212页的例子页的例子生成了两个类,生成了两个类,A A和和B B,分别有,分别有foo()foo()和和bar()bar()方法。这两种方法在调用其他方法。这两种方法在调用其他类的方法前有一个短暂的停顿。主类
48、,名为类的方法前有一个短暂的停顿。主类,名为DeadlockDeadlock,创建了,创建了A A和和B B的实例,的实例,然后启动第二个线程去设置死锁环境。然后启动第二个线程去设置死锁环境。foo()foo()和和bar()bar()方法使用方法使用sleep()sleep()强强迫死锁现象发生。迫死锁现象发生。程序死锁,需要按程序死锁,需要按CTRL-CCTRL-C来结束程序。在来结束程序。在PCPC机上按机上按CTRL-BREAKCTRL-BREAK(或在(或在SolarisSolaris下按下按CTRL-CTRL-)可以看到全线程和管程缓冲堆。)可以看到全线程和管程缓冲堆。Return
49、8.6 8.6 线程的控制线程的控制 本节讨论有关线程控制的问题,包括线程的挂起、本节讨论有关线程控制的问题,包括线程的挂起、恢复、终止等方面的问题。恢复、终止等方面的问题。8.6.1 8.6.1 挂起、恢复和终止线程挂起、恢复和终止线程8.6.2 Java 28.6.2 Java 2中的线程控制中的线程控制8.6.3 8.6.3 使用使用instanceofinstanceofReturn8.6.1 8.6.1 8.6.1 8.6.1 挂起、恢复和终止线程挂起、恢复和终止线程挂起、恢复和终止线程挂起、恢复和终止线程 有有时时,线线程程的的挂挂起起是是很很有有用用的的。例例如如,一一个个独独立
50、立的的线线程程可可以以用用来来显显示示当当日日的的时时间间。如如果果用用户户不不希希望望用用时时钟钟,线线程程被被挂挂起起。在在任任何何情情形形下下,挂挂起起线线程程是很简单的,一旦挂起,重新启动线程也是一件简单的事。是很简单的,一旦挂起,重新启动线程也是一件简单的事。挂挂起起、终终止止和和恢恢复复线线程程机机制制在在Java Java 2 2和和早早期期版版本本中中有有所所不不同同。尽尽管管你你运运用用Java Java 2 2的的途途径径编编写写代代码码,仍仍需需了了解解这这些些操操作作在在早早期期JavaJava环环境境下下是是如如何何完完成成的的。例例如如,也也许许你你需需要要更更新新