java多线程编程的专业理解_第1页
java多线程编程的专业理解_第2页
java多线程编程的专业理解_第3页
java多线程编程的专业理解_第4页
java多线程编程的专业理解_第5页
已阅读5页,还剩23页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、第11章多线程编程和其他多数计算机语言不同,java内置支持多线程编程(multithreaded programming 多线程程序包含两条或两条以上并发运行的部分。程序中每个这样的部分都叫一个线程 (thread),每个线程都有独立的执行路径。因此,多线程是多任务处理的一种特殊形式。你一定知道多任务处理,因为它实际上被所有的现代操作系统所支持。然而,多任务 处理有两种截然不同的类型:基于进程的和基于线程的。认识两者的不同是i-分重要的。 对很多读者,基于进程的多任务处理是更熟悉的形式。进程(process)本质上是一个执行的 程序。因此,基丁进程(process-based)的多任务处理的

2、特点是允许你的计算机同吋运行两 个或更多的程序。举例來说,基于进程的多任务处理使你在运用文本编辑器的时候可以同 时运行java编译器。在基于进程的多任务处理屮,程序是调度程序所分派的最小代码单位。在基于线程(thread-based)的多任务处理环境屮,线程是最小的执行单位。这意味着一 个程序可以同时执行两个或者多个任务的功能。例如,一个文木编辑器可以在打印的同时 格式化文本。所以,多进程程序处理“大图片”,而多线程程序处理细节问题。多线程程序比多进程程序需要更少的管理费川。进程是重量级的任务,需要分配它们 自己独立的地址空间。进程间通信是昂贵和受限的。进程间的转换也是很需要花费的。另 -方而

3、,线程是轻量级的选手。它们共享相同的地址空间并11共同分享同一个进程。线程 间通信是便宜的,线程间的转换也是低成木的。当java程序使用多进程任务处理环境时, 多进程程序不受java的控制,而多线程则受java控制。多线程帮助你写出cpu最大利用率的高效程序,因为空闲时间保持最低。这xjjava运 行的交互式的网络互连环境是至关重要的,因为空闲时间是公共的。举个例子来说,网络 的数据传输速率远低于计算机处理能力,木地文件系统资源的读写速度远低于cpu的处理 能力,当然,用户输入也比计算机慢很多。在传统的单线程环境中,你的程序必须等待每 一个这样的任务完成以后才能执行下一步一一尽管cpu有很多空

4、闲吋间。多线程使你能够 获得并充分利用这些空闲时间。如果你在windows 98或windows 200()这样的操作系统下有编程经验,那么你已经熟 悉了多线程。然而,血va管理线程使多线程处理尤其方便,因为很多细节对你来说是易于 处理的。11.1 java线程模型java运行系统在很多方面依赖于线程,所有的类库设计都考虑到多线程。实际上,java 使用线程来使整个坏境界步。这有利于通过防止cpu循坏的浪费来减少无效部分。为更好的理解多线程环境的优势可以将它与它的对照物相比较。单线程系统的处理途 径是使用一种叫作轮询的事件循环方法。在该模型小,单线程控制在一无限循环屮运行,轮询一个事件序列来决

5、定卜一步做什么。一旦轮询装直返回信号表明,准备好读取网络 文件,事件循环调度控制管理到适当的事件处理程序。总到事件处理程序返回,系统中没 冇其他事件发生。这就浪费了cpu时间。这导致了程序的一部分独占了系统,阻止了其他 事件的执行。总的来说,单线程环境,当一个线程因为等待资源时阻塞(block,挂起执行), 整个程序停止运行。java多线程的优点在于取消了主循环/轮询机制。一个线程町以暂停而不影响程序的其 他部分。例如,当一个线程从网络读取数据或等待用户输入时产生的空闲时间可以被利用 到英他地方。多线程允许活的循环在每一帧间隙中沉睡一秒而不暂停整个系统。在java程 序中出现线程阻塞,仅有一个

6、线程暂停,其他线程继续运行。线程存在于好儿种状态。线程可以正在运行(running)。只要获得cpu时间它就可以 运行。运行的线程可以被挂起(suspend),并临时屮断它的执行。一个挂起的线程可以被 恢复(resume,允许它从停止的地方继续运行。一个线程可以在等待资源时被阻塞(block)。 在任何时候,线程可以终上(terminate),这立即屮断了它的运行。一口终止,线程不能 被恢复。11.1.1线程优先级java给每个线程安排优先级以决定与其他线程比较时该如何对待该线程。线程优先级 是详细说明线程间优先关系的整数。作为绝对值,优先级是毫无意义的;当只有一个线程 吋,优先级高的线程并不

7、比优先权低的线程运行的快。相反,线程的优先级是用來决定何 吋从一个运行的线程切换到另一个。这叫“上下文转换”(context switch)。决定上下文转换 发生的规则很简单:线程可以h动放弃控制。在i/o未决定的情况卜,睡眠或阻塞由明确的讣步來完成。 在这种假定下,所有其他的线程被检测,准备运行的最高优先级线程被授tcpuc线程可以被高优先级的线程抢山。在这种悄况下,低优先级线程不主动放弃,处理 器只是被先占无论它正在干什么一一处理器被高优先级的线程占据。基木上, 一旦高优先级线程要运行,它就执行。这叫做有优先权的多任务处理。当两个相同优先级的线程竞争cpu周期时,情形有一点复杂。对于win

8、dows98这样的 操作系统,等优先级的线程是在循环模式下自动划分时间的。对于其他操作系统,例如 solaris 2.x,等优先级线程相对于它们的对等体h动放弃。如果不这样,其他的线程就不会 % z *坯仃。警告:不同的操作系统下等优先级线程的上下文转换可能会产生错误。11.1.2同步性因为多线程在你的程序中引入了一个异步行为,所以在你需要的时候必须冇加强同步 性的方法。举例来说,如果你希望两个线程相互通信并共享一个复杂的数据结构,例如链 表序列,你需要某些方法来确保它们没有相互冲突。也就是说,你必须防止一个线程写入 数据而另一个线程正在读取链表屮的数据。为此目的,jav“在进程间同步性的老模

9、式基础上实行了另一种方法:管程(monitor) o管程是一种由c.a.r.hoare首先定义的控制机制。 你可以把管程想象成一个仅控制一个线程的小盒子。一旦线程进入管程,所有线程必须等 待直到该线程退出了管程。用这种方法,管程可以用来防止其享的资源被多个线程操纵。很多多线程系统把管程作为程序必须明确的引用和操作的对象。java提供一个清晰的 解决方案。没有“monitor"类;相反,每个对象都拥有自己的隐式管程,当对象的同步方 法被调用时管程口动载入。一旦一个线程包含在一个同步方法中,没有其他线程可以调用 相同对象的同步方法。这就使你可以编写非常清晰和简洁的多线程代码,因为同步支持

10、是 语言内置的。11.1.3消息传递在你把程序分成若干线程后,你就要定义各线程之间的联系。用大多数其他语言规划 时,你必须依赖于操作系统來确立线程间通信。这样当然增加花费。然而,java捉供了多 线程间谈话清洁的、低成木的途径通过调用所有对象都有的预先确定的方法。java的 消息传递系统允许一个线程进入一个对象的一个同步方法,然后在那里等待,直到其他线 程明确通知它出來。11.1.4 thread 类和runnable 接口java的多线程系统建立于thread类,它的方法,它的共伴接口runnable®础上。thread 类封装了线程的执行。既然你不能直接引用运行着的线程的状态,你

11、要通过它的代理处理 它,于是thread实例产生了。为创建一个新的线程,你的程序必须扩展thread或实现 runnable接 口。thread类定义了好几种方法来帮助管理线程。木章用到的方法如表111所示:表门管理线程的方法方法意义getname获得线程名称getpriority获得线程优先级jsalivc判定线程是否仍在运行join等待一个线程终止run线程的入口点.sleep在一段时间内挂起线程start通过调用运行方法來启动线程到目前为止,本书所应用的例子都是用单线程的。本章剩余部分解释如何用thread和 runnable來创建、管理线程。让我们从所有java程序都有的线程:主线程开

12、始。11.2主线程当java程序启动时,一个线程立刻运行,该线程通常叫做程序的主线程(main thread),因为它是程序开始时就执行的。主线程的重要性体现在两方面:它是产生其他子线程的线程通常它必须最后完成执行,因为它执行各种关闭动作。尽管主线程在程序启动时自动创建,但它可以由一个thread対象控制。为此,你必须 调用方法currentthread()获得它的一个引用,currentthread()mthread类的公有的静态成员。 它的通常形式如下:static thread currentthread()该方法返冋一个调用它的线程的引用。一口你获得主线程的引用,你就可以像控制其 他线

13、程那样控制主线程。让我们从复习下面例题开始:/ controlling the main threadclass currentthreaddemo public static void main(string args)thread t = thread.currentthread();system, out. print in ( h current thread: 11 + t);/ change the name of the threadt setname ( nmy thread11);system, out print in ( "after name change :

14、 f, + t);try for (int n = 5; n > 0; n )system.out.printin(n);thread s丄eep(1000); catch (interruptedexception e) system, out print丄n ( "main thread interrupted'1);在本程序中,当前线程(自然是主线程)的引用通过调用currentthread(m,该引用 保存在局部变量t屮。然后,程序显示了线程的信息。接着程序调用setname()改变线程的内 部名称。线程信息又被显示。然后,一个循环数从5开始递减,每数一次暂停一

15、秒。暂停是 由sleep()方法來完成的。sleepo语句明确规定延迟时间是1毫秒。注总循环外的try/catch块。 thread类的sleep。方法可能引发一个interruptedexception异常。这种情形会在其他线程想要 打搅沉睡线程时发纶。本例只是打印了它是否被打断的消息。在实际的程序中,你必须灵 活处理此类问题。下面是本程序的输出:current thread: threadmain,5,mainafter name change: threadimy thread, 5,main543注意t作为语句println()«|参数运用时输出的产也 该显示顺序:线程名称,

16、优先级以及 组的名称。默认情况下,主线程的名称是main。它的优先级是5,这也是默认值,main也是 所属线程组的名称。一个线程组(thwadgro叩)是一种将线程作为一个整体集合的状态控 制的数据结构。这个过程由专有的运行时环境来处理,在此就不赘述了。线程名改变后,( 乂被输出。这次,显示了新的线程名。让我们更仔细的研究程序中thread类定义的方法。sleep。方法按照毫秒级的吋间指示使 线程从被调用到挂起。它的通常形式如下:static void sleep(long milliseconds) throws interruptedexception挂起的时间被明确定义为毫秒。该方法可能

17、引interruptedexception异常。sleep()方法还有第二种形式,显示如下,该方法允许你指定时间是以毫秒还是以纳秒 为周期。static void sleep (long milliseconds, int nanoseconds) throwsinterruptedexception第二种形式仅当允许以纳秒为时间周期时可用。如上述程序所示,你可以用setname()设置线程名称,用getname()来获得线程名称(该 过程在程序中没有体现)。这些方法都是thread类的成员,声明如下:void setname(string threadname)string getname(

18、)这里,threadname特指线程名称。11.3创建线程大多数情况,通过实例化一个thread对彖来创建一个线程。java定义了两种方式:实现runnable 接口。可以继承thread类。下面的两小节依次介绍了每一种方式。11.3.1 实现 runnable 接口创建线程的最简单的方法就是创建一个实现runnable接口的类。runnable抽彖了一个 执行代码单元。你可以通过实现runnable接口的方法创建每一个对象的线程。为实现 runnable接口,一个类仅需实现一个run()的简单方法,该方法声明如下:public void run()在nm()中对以定义代码来构建新的线程。理解

19、卜.而内容是至关重要的:run()7z法能够像主线程那样调川其他方法,引用其他类,声明变量。仅有的不同是en()在程序屮确立另 一个并发的线程执行入口。当mn()返回吋,该线程结束。在你已经创建了实现runnable接口的类以后,你要在类内部实例化一个thread类的对 象。thread类定义了好几种构造函数。我们会用到的如下:thread(runnable threadob, string threadname)该构造函数中,threadob是一个实现runnable接口类的实例。这定义了线程执彳亍的起点。 新线程的名称曲threadn ame定义。建立新的线程后,它并不运行直到调用了它的s

20、tan()方法,该方法在thread类中定义。 本质上,start()执行的是个对run()的调用。start ()方法声明如下:void start ()下面的例了是创建一个新的线程并启动它运行:/ create a second threadclass newthread implements runnable thread t;newthread()/ create a new, second threadt = new thread (this, ndemo thread11);system.out.printin(hchiid thread: h + t);tstart (); /

21、start the thread/ this is the entry point for the second thread.public void run ()try for (int i = 5; i > 0; i )system.out.printin(nchild thread: n + i);thread.sleep(500); catch (interruptedexception e) system.out printin(hchild interrupted“);system.out.printin(nexiting child thread.n);class thre

22、addemo public static void main(string args)new newthread(); / create a new threadtry for (int i = 5; i > 0; i )system.out.printinhmain thread: " + i);thread sleep(1000); catch (interruptedexception e) system, out print in ( nmain thread interrupted.f,);system.out.printin(hmain thread exiting

23、n);在newthread构造两数屮,新的thread对象由卜面的语句创建:t = new thread (this, 11 demo thread11);通过前面的语句this表明在this对象屮你想要新的线程调用rim()方法。然后,start()被 调用,以wn()方法为开始启动了线程的执行。这使子线程for循环开始执行。调用sian()之 后,newthread的构造函数返回到main()o当主线程被恢复,它到达for循环。两个线程继 续运行,共享cpu,直到它们的循环结束。该程序的输出如下:child thread: threaddemo thread,5z mainmain thr

24、ead: 5child thread: 5child thread: 4main thread: 4child thread: 3child thread: 2main thread: 3child thread: 1exiting child threadmain thread: 2main thread: 1main thread exiting.如前面提到的,在多线程程序中,通常主线程必须是结束运行的最后一个线程。实际 上,一些老的jvm,如果主线程先于子线程结束,java的运行时间系统就可能“挂起”。 前述程序保证了主线程最后结束,因为主线程沉睡周期1000毫秒,而子线程仅为500毫秒

25、。 这就使子线程在主线程结束之前先结束。简而言之,你将看到等待线程结束的更好途径。11.3.2 扩展 thread创建线程的另一个途径是创建一个新类来扩展thread类,然后创建该类的实例。当一 个类继承thread时,它必须重载rim()方法,这个nm()方法是新线程的入口。它也必须调用 start()方法去启动新线程执行。下面用扩展thread类重写前面的程序:/ create a second thread by extending threadclass newthread extends thread newthread ()/ create a new, second thread

26、super ("demo thread11);system, out. print in ( n chi id thread: f, + t his);start (); / start the thread/ this is the entry point for the second thread-public void run ()try for(int i = 5; i > 0; i-) system, out. print in (f, chi id thread: f, + i);thread.sleep(500); catch (interruptedexcept

27、ion e) system.out printin(nchild interrupted);system.out.printin(nexiting child thread.u);class extendthread public static void main(string args) new newthread(); / create a new threadtry for(int i = 5; i > 0; i-) system<out .println (f,main thread: n + i); thread.sleep(1000); catch (interrupt

28、edexception e) system.out.printin(hmain thread interrupted.n);system.out.printin("main thread exitingn);)该程序生成和前述版本和同的输出。子线程是由实例化newthread对象生成的,该对 彖从thread类派生。注意newthread中super()的调用。该方法调用了下列形式的thread构 造函数:publie thread (string threadname)这里,threadname指定线程名称。11.3.3选择合适方法到这里,你一定会奇怪为什么java有两种创建子线程

29、的方法,哪一种更好呢。所有的 问题都归于一点。thread类定义了多种方法可以被派生类重载。对于所有的方法,惟一的 必须被重载的是nm()方法。这当然是实现runnable接口所需的同样的方法。很多java程序员 认为类仅在它们被加强或修改时应该被扩展。因此,如果你不重载thread的其他方法时, 最好只实现runnable接口。这当然由你决定。然而,在本章的其他部分,我们应用实现 runnable接口的类來创建线程。11.4创建多线程到目前为止,我们仅用到两个线程:主线程和一个了线程。然而,你的程序可以创建 所需的更多线程。例如,下面的程序创建了三个子线程:/ create multiple

30、 threads- class newthread implements runnable string name; / name of threadthread t;newthread(string threadname) name = threadname;t = new thread(this, name);system, out. print in (f,new thread: n + t);t.start ); / start the thread/ this is the entry point for thread.public void run ()try for (int i

31、 = 5; i > 0; i-) system .out .print in (name + n : f, + i);thread-sleep(1000); catch (interruptedexception e) system.out.printinname + ninterrupted");system.out.printin(name + n exiting.n);class muitithreaddemo public static void main(string args)new newthread (,f0nen ) ; / start threads new

32、 newthread(ntwon);new newthread (f,threen);try / wait for other threads to endthread.sleep(10000); catch (inptedexception e) system.out .printin ( nmain thread interrupted'1); system.out.printin(nmain thread exiting.n);程序输出如下所示:new thread: threadone,5,mainnew thread: threadtwo,5,mainnew thread:

33、threadthree,5,mainone: 5two: 5three: 5one: 4two: 4three: 4one: 3three: 3two: 3one: 2three : 2two: 2one: 1three: 1two: 1one exiting.two exiting.three exitingmain thread exiting如你所见,一口启动,所有三个子线程共亨cpu。注意main()'px'jsleep( 10000)的调用。 这使主线程沉睡十秒确保它最后结束。11.5 使用 isali ve()和 join()如前所述,通常你希望主线程最后结束。在前

34、而的例子中,这点是通过在niain()中调 用sleep()来实现的,经过足够长时间的延迟以确保所有子线程都先于主线程结束。然而, 这不是一个令人满意的解决方法,它也带來一个人问题:一个线程如何知道另一-线程己经 结束?幸运的是,thread类提供了回答此问题的方法。冇两种方法町以判定一个线程是否结束。第一,町以在线程中调用isalive()0这种方法 由thread定义,它的通常形式如下:final boolean isalive ()如果所调用线程仍在运行,isalive()方法返冋true,如果不是则返冋falseo 但isalive ()很少用到,等待线程结束的更常用的方法是调用joi

35、n(),描述如下:final void join( ) throws interruptedexception该方法等待所调用线程结束。该名字来自于要求线程等待肯到指定线程参与的概念。 join。的附加形式允许给等待指定线程结束定义一个最人时间。下面是前面例子的改进版本。运用join()以确保主线程最后结束。同样,它也演示了 isalive()方法。/ using join() to wait for threads to finish.class newthread implements runnable string name; / name of threadthread t;newth

36、read (string threadname) name = threadnane;t = new thread (this, name);system.out printin("new thread: n + t);tstart (); / start the thread/ this is the entry point for thread public void run ()try for (int i = 5; i > 0; i-) system .out .print in (name + n : f, + i);thread.sleep(1000); catch

37、 (interruptedexception e) system.out.printin(name + n interrupted.n);system.out.printin(name + n exiting.n);class demojoin public static void main(string args)newthread obi = new newthread (ffonen );newthread ob2 = new newthread (ntwof,);newthread ob3 = new newthread(nthreen);system.out.printin(nthr

38、ead one is alive: n+ obi.t.isalive();system.out.printin(nthread two is alive: u+ ob2tisalive();system.out.printin(nthread three is alive: h+ ob3tisalive();/ wait for threads to finishtry system, out. printin (f,wai ting for t hreads to finish . n); obitjoin ();ob2tjoin ();ob3tjoin (); catch (inptede

39、xception e) system.out .printin ( nmain thread interrupted'1);system.out.printin(nthread one is alive: n+ obi.t.isalive();system.out.printin(nthread two is alive: n+ ob2tisalive ();system.out.printin(nthread three is alive: h+ ob3tisalive();system, out. printin ( nmain thread exiting .11);程序输出如下

40、所示:new thread: threadone,5z mainjnew thread: threadtwo,5,mainnew thread: threadthree,5,mainthread one is alive: truethread two is alive: truethread three is alive: truewaiting for threads to finishone: 5two: 5three: 5one: 4two: 4three : 4one: 3two: 3three: 3one: 2two: 2three : 2one: 1two: 1three: 1t

41、wo exiting.three exiting-one exiting.thread one is alive: falsethread two is alive: falsethread three is alive: falsemain thread exiting.如你所见,调用join()后返冋,线程终止执行。11.6线程优先级线程优先级被线程调度用來判定何时每个线程允许运行。理论上,优先级高的线程比 优先级低的线程获得更多的cpu吋间。实际上,线程获得的cpu吋间通常山包括优先级在 内的多个因索决定(例如,一个实行多任务处理的操作系统如何更冇效的利用cpu时间)。 一个优先级高的线

42、程h然比优先级低的线程优先。举例来说,当低优先级线程正在运行, 而一个高优先级的线程被恢复(例如从沉睡中或等待i/o中),它将抢占低优先级线程所使 用的cpu。理论上,等优先级线程有同等的权利使用cpu。但你必须小心了。记住,血va是被设 计成能在很多环境下工作的。一些环境下实现多任务处理从本质上与其他环境不同。为安 全起见,等优先级线程偶尔也受控制。这保证了所有线程在无优先级的操作系统下都有机 会运行。实际上,在无优先级的环境下,多数线程仍然有机会运行,因为很多线程不可避 免的会遭遇阻塞,例如等待输入输出。遇到这种情形,阻塞的线程挂起,具他线程运行。 但是如果你希望多线程执行的顺利的话,最好

43、不要采用这种方法。同样,有些类型的任务 是占cpu的。对于这些支配cpu类型的线程,有时你希望能够支配它们,以便使其他线程 可以运行。设置线程的优先级,用setpriority()方法,该方法也是tread的成员。它的通常形式为:final void setpriority (int level)这里,level指定了对所调用的线程的新的优先权的设置。level的值必须在 min_priority至ljmax_priority范围内。通常,它们的值分别是1和10。要返回一个线程为默认的优先级,指定norm_priority,通常值为5。这些优先级在thread'i*都被定义 为fina

44、l型变量。你可以通过调用thread的gctpriority()方法来获得当前的优先级设置。该方法如下:final int getpriority)当涉及调度时,java的执行可以有本质上不同的行为。windows 95/98/nt/2000的丁作 或多或少如你所愿。但其他版木可能工作的完全不同。大多数矛盾发生在你使川冇优先级 行为的线程,而不是协同的腾出cpu时间。最安全的办法是获得可预先性的优先权,java 获得跨平台的线程行为的方法是自动放弃对cpu的控制。下面的例子阐述了两个不同优先级的线程,运行于具有优先权的平台,这与运行于无 优先级的平台不同。一个线程通过thread.norm_p

45、riority设置了高于普通优先级两级的 级数,另一线程设置的优先级则低于普通级两级。两线程被启动并允许运行10秒。每个线 程执行一个循环,记录反复的次数。10秒后,主线程终止了两线程。每个线程经过循坏的 次数被显示。/ demonstrate thread priorities.class clicker implements runnable int click = 0;thread t;private volatile boolean running = true;public clicker (int p) t = new thread (this); tsetpriority(p);

46、public void run ()while (running) click+;public void stop()running = false;public void start () tstart );class hilopri public static void main(string args) threadcurrentthread().setpriority(threadmax_priority); clicker hi = new clicker(thread.norm_priority + 2); clicker lo = new clicker(thread.norm_

47、priority - 2);lostart();hi start();try thread.sleep(10000); catch (interruptedexception e) systemout.printin(nmain thread interruptedn);lo-stop );histop );/ wait for child threads to terminate.try hi.t.join ();lo-t.join(); catch (interruptedexception e) system.out .printin (f,interruptedexception ca

48、ughtn );system.out.printin(nlow-priority thread: n + lo.click); system.out.printin("high一priority thread: n + hiclick);该程序在windows 98 k运行的输出,表明线程确实上下转换,其至既不屈从于cpu, 也不被输入输出阻塞。优先级高的线程获得大约90%的cpu时间。low-priority thread: 4408112high-priority thread: 589626904当然,该程序的精确的输出结果依赖于你的cpu的速度和运行的其他任务的数量。当 同样

49、的程序运行于无优先级的系统,将会有不同的结果。上述程序还有个值得注意的地方。注意running前的关键字volatile0尽管volatile在卜"章 会被很仔细的讨论,用在此处以确保running的值在下面的循环中每次都得到验证。while (running) click+;如果不用volatile, java nj以口由的优化循环:running的值被存在cpu的一个寄存器中, 每次重复不一定需要复检。volatile的运用阻止了该优化,告知java running可以改变,改变 方式并不以直接代码形式显示。11.7线程同步当两个或两个以上的线程需要共享资源,它们需要某种方法來确

50、定资源在某一刻仅被 一个线程占用。达到此冃的的过程叫做同步(synchronization)。像你所看到的,java为此 提供了独特的,语言水平上的支持。同步的关键是管程(也叫信号ft semaphore)的概念。管程是一个互斥独占锁定的对象, 或称互斥体(mutex)。在给定的时间,仅有一个线程可以获得管程。当一个线程需要锁定, 它必须进入管程。所冇其他的试图进入己经锁定的铮程的线程必须挂起直到笫一个线程退出管程。这些具他的线程被称为等待管程。-个拥有管程的线程如果愿意的话町以再次进 入相同的管程。如果你用其他语言例如c或c+时用到过同步,你会知道它用起来冇一点诡异。这是因 为很多语言它们白

51、己不支持同步。相反,对同步线程,程序必须利用操作系统源语。幸运 的是java通过语言元素实现同步,大多数的与同步相关的复杂性都被消除。你可以用两种方法同步化代码。两者都包括synchronized关键字的运用,下而分别说明 这两种方法。11.7.1使用同步方法java中同步是简单的,因为所有对象都有它们与z对应的隐式管程。进入某一对象的 管程,就是调用被synchronized关键7修饰的方法。当一个线程在-个同步方法内部,所有 试图调用该方法(或其他同步方法)的同实例的英他线程必须等待。为了退出铮程,并放 弃对对象的控制权给其他等待的线程,拥有管程的线程仅需从同步方法中返回。为理解同步的必要

52、性,让我们从一个应该使用同步却没有用的简单例子开始。下而的 程序有三个简单类。首先是callme,它有一个简单的方法call( ) call()方法有一个名为msg 的string参数。该方法试图在方括号内打印msg字符串。有趣的事是在调用call()打印左括 号和msg字符串后,调用thread.sleep(looo),该方法使当前线程暂停1秒。下一个类的构造函数caller,引用了callme的一个实例以及一个string,它们被分别存 在怡get和msg中。构造函数也创建了一个调用该对象的nm()方法的新线程。该线程立 即川动。caller类的run()方法通过参数msg字符申调用cal

53、lme实例target的call()方法。最后, synch类由创建callme的一个简单实例和caller的三个具有不同消息字符串的实例开始。 callme的同一实例传给每个caller实例。/ this program is not synchronized.class callme void call (string msg) system.out print(nn + msg);try thread sleep1000); catch(interruptedexception e) system, out print in hi interrupted n );system.out p

54、rintin ( nn);class caller implements runnable string msg;callme target;thread t;public caller(callme targ, string s) target = targ;msg = s;t = new thread(this);t.start ();public void run ()target.call(msg);)class synch public static void main(string args) callme target = new callme();caller obi = ne

55、w caller(target, uhellon);caller ob2 = new caller(target, "synchronized");caller ob3 = new caller (targe"world”);/ wait for threads to endtry obltjoin ();ob2tjoin ();ob3tjoin (); catchinterruptedexception e) system.out.printin(ninterrupted0);)该程序的输出如下:hellosynchronizedworld在本例中,通过调用sl

56、eep(), call()方法允许执行转换到另一个线程。该结果是三个消 息字符串的混合输出。该程序中,没有阻止三个线程同吋调用同一对象的同一方法的方法 存在。这是一种竞争,因为三个线程争着完成方法。例题用slccp()使该影响璽复和明显。 在人多数情况,竞争是更为复杂和不可预知的,因为你不能确定何时上下文转换会发生。 这使程序时而运行正常时而出错。为达到上例所想达到的冃的,必须有权连续的使用call( )o也就是说,在某一时刻,必 须限制只有一个线程可以支配它。为此,你只需在call()定义前加上关键字synchronized, 如下:class callme synchronized void call(string msg) 这防止了在一个线程使用c

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论