《Java多线程设计模式研究.pdf》由会员分享,可在线阅读,更多相关《Java多线程设计模式研究.pdf(4页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、2 0 0 6 年第 1 1 期 计算机与现代化 J I S U A N J I Y U X I A N D A 珊U A 总第 1 3 5 期 文章编号:1 0 0 6-2 4 7 5(2 0 0 6)1 1 (D)2-0 3 J a v a 多线程设计模式研究 崔立剑,吴平(中国农业大学信息与电气工程学院,北京1 0 0 0 8 3)摘要:针对 J a v a多线程并发程序设计,归类和总结了不同功能的 J a v a 多线程设计模式,结合实例分析比较 了这些设计模 式,这有助于解决实际编程中的并发问题,开发出高质量的J a v a 多线程程序。关键词:多线;s y n c h r o n
2、i z e d;读 写锁;生产者 消费者 中图分类号:T P 3 1 1 5 2 文献标识码:A S t u d y o n J a v a Mu l t i-t h r e a d De s i g n P a t t e r n s C U I L i-j i a n,WU P i n g (C o l l e g e o f I n f o r m a t i o n a n d E l e c t r i c a l E n g i n e e r i n g,C h i n a a g r l e d t u _,a l U niv e r s i t y,B e i j i ng
3、1 0 0 0 8 3,C hi na)b s a c t:I n o r d e rt o d e v e l o p J a v a c o n c u r r e n t p r o g r a ms J a v amu l t i t h r e a d d e s i g n p a t t e r n s o f d i ff e r e n t f u n c t i o n s a re c l a s s i f i e d a n d$u n l ma ri z e dT h e s e p a t t e r n s a r e a I l a l y z e d a n
4、 d c o mp a r e d b,r e x a mp l e s,wh i c h c a n b e u s e f ul f o r s o l v i ng con c u r r e n t p r o b l e ms o f J a v a p r o g r mn mi n g and d e v e l o p i n g J a v a mult i t h r e a d p r o g r a ms of hig h q u ty Ke y wo r d s:multi t h r e a d;s y n c h r o niz e d;r e a d-w r i
5、 t e l o c k;p r o d u c e r-c o n s u me r 0 引 言 近年来,随着多处理机体系结构的演变和分布式 并行系统的发展,并发多任务的程序设计技术已愈来 愈显示其重要性,多线程设计模式在这些技术的发展 中 起到重要作用,一方面,出现了A d a、J a v a、C#等支持 并发多任务的程序设计语言;另一方面,许多操作系统 都采用多线程结构,提供了内核级的多线程支持。J a v a 语言 J、,M(J a v a 虚拟机)提供完全意义上的多 线程支持,其内置的语言级多线程控制可以保证多个 并发例程通过多线程编程实现,从语言级上提供各个 线程间协调工作的调度能
6、力,降低并发程序设计的难 度。但是在程序中,所有线程共用相同的内存空间,两 个或多个线程可能同时访问同一变量或运行同一对象 的同一方法,这将导致数据访问冲突以及数据前后不 一致,同时线程之间不恰当的同步会导致死锁。这就 需要对不同功能的多线程程序进行归类,总结各种设 计模式,针对这些模式来避免上述现象的发生。本文针对以上问题的出现,以Win d o w s 2 0 0 0 为宿 主平台,J D K(I 4)为 J a v a 开发环境和运行系统,分析 与探讨 J a v a 多线程程序功能设计,归类 J a v a 多线程 设计模式以及编程策略。1 多个线程共享实例 在多线程程序设计中,通常都
7、会有多个线程共享 处理数据或事件。如果这些线程无秩序地并发处理 共享数据,结果将会出现错误,甚至死锁。临界资源 就是程序中不能被多个线程并发执行的代码段,可以 是方法,也可以是语句块,多个线程只能互斥地进入 临界资源,在任一时刻,只允许一个线程能真正地进 入临界资源,而其它线程必须排队等待。J a v a 采用 s y n c h r o n i z e d 关键字来定义临界资源。以下针对此类 型下不同功能的程序设计模式进行讨论。1 1 实例状态不变 S y n c h r o n iz e d 的使用是受 限制的,并非 s ync h r o n i z e d 使用得越多越好。S ync
8、h r o n i ze d 语句会影响程序 收稿 日期:2 0 o 5 1 2 1 2 作者简介:崔立剑(1 9 8 2-),男,山东东阿人,中国农业大学信息与电气工程学院硕士研究生,研究方向:计算机应用技术;吴平(1 9 5 3 ),女,教授,研究方向:专家系统与软件工程。维普资讯 http:/ 2 0 0 6年第 l 1 期 崔立剑等:J a v a多线程设计模式研究 9 3 的执行性能。如果实例的状态不会改变的话,即该类 的字段值无法直接更改或没有用来更改字段值的方 法时,这种类称为 I m m u ta b l e 类。在这种情况下,尽量 不用 s y n c h r o n i z
9、 e d方法建立临界资源管理线程,而改 用 I m m u t a b l e 模式进行线程调度。下面以 s y n c h r o n iz e d(t h i s)为例进行分析,示例程 序中主要有两个基本方法:s ync 和 n o m ync。这两个 方法全部基于 I m m u t a b l e 类构建,功能完全相同,语句 也大致相同,只是在 s ync 方法中增加了 s ync h r o n i z e d (th is)语句。在 m a in()方法中,分别对 s ync 和 n o mync 执行相同次数的调用,从而对它们各自消耗的时间进 行比较。如图 1 所示。图 1 为
10、M a in ia v a 的执行结果。从图 1 中可以看 出,s ync 方法的执行时间显然比相应的 n o m ync 方法 的执行时间长。因为在程序运行时,s ync h r o n i z e d 语句 被编译成 m o ni t o r e n te r 和 m o n i to r e x it 等多条语句,无论 操作是正常结束还是异常结束,都要有上锁(1 o c k)和 解锁(u n l o c k)的过程,所以执行时间会有所延长。啊圈曩啜嘲嘲盈一=:J 一 ;:鞭 盘:藉 黎 tg帅 h j 辨 chH ie 嚣 穰 l 苦幂 薹 嚣 鬻瓣 e h 辨t h 脚h l “稀:霉
11、辨t h e h 脚 l 万豫:臻累 耗 碍 l 们。斡:、¥w,图 1 M a i n i a v a 执行结果 1 2 实例状态改变 当实例的状态因为改变而使线程运行不合适时,譬如出现死锁状态,就要求一部分线程进行等待,直 到死锁解除,也就是“适当的状态”后,这部分线程才 允许执行。在这里,以“警戒条件”来表示实例中的“适当的状态”。第一种方法,在进行实例改变操作前,检查警戒条件是否 被满足。如果警戒条件不成立,则要求线程等待直到警戒条 件成立为止。在 J a v a 程序设计中,检验警戒条件时使用 w h i l e 语句,线程等待则使用 w a i t 方法。并使用 n o n o t
12、 lf y A l l 通知 警戒条件的改变。第二种方法,首先也要在进行实例改变操作前,检查警戒 条件是否被满足。只有在警戒条件成立时,才会继续执行;如 果警戒条件不成立,则直接中断执行,立即退出。J a v a 语 言 中,检验警戒条件使用 i f 语句。当要中断时,可使用 r e t l l n l 退 出方法,或使用 t h r o w抛出异常。两种方法中,检验、修改警戒条件时,都使用到 s ync h r o n iz e d 语句。1 3 读 写锁模式(R e a d Wr i t e L o c k)在多线程共用一个实例时,也会出现参考实例状 态的线程(R e a d 线程)和改变
13、实例状态的线程(W ri t e 线程),这就是读 写锁模式。实现过程是,将 R e a d 线程与 W ri te 线程分开,加 入R e a d W r i t e I_o c k 参与者,以提供两种不同的锁定。R e a d Wr i t e I_o c k 参与者会对 R e a d 线程和 W ri t e 线程进 行互斥控制。R e a d Wr i t e l_o c k 参与者进行共享互斥的 操作时,用到实例状态改变的设计模式。当完全没有 W ri t e 线程时,就使用实例状态不变的设计模式。2 线程之间数据传输 这里针对的设计模式是解决经典的生产者 消费 者问题(P r o
14、 d u c e r-con s u m e r)。P r o d u c e r 是“生产者”,指生产数据的线程。C o n s u m e r 是“消费者”,指使用数据的线程。生产者必须 将数据安全地交给消费者,但当生产者与消费者在不 同线程上运行时,两者处理速度差将是最大的问题。例如当消费者要取数据时生产者还没建立出线程,或 是生产者建立出数据时消费者的状态还没办法接受 数据等等。在生产者和消费者之间加入一个“桥梁”(C h a rt n e 1),以这个桥梁来缓冲生产者和消费者之间的处理 速度差。C h a n n e l 会从 P r o d u c e r 处接收数据,并保管起来。
15、并应 C o n s u m e r 的要求,将数据传送出去。为了确保 安全性,P ro d u c e r 与 C o n s ume r 要对访 问进行共享排 斥。当 P ro d u c e r 将数据传给 C h a n n e l 时,若 Cha n n d的 状态无法接受数据,这时 P r o d u c e r 会在 C h a n n e l 的状 态变成可以接受之前,保持等待状态。当 Con s u m e r 从 C h a n n e l 获取数据的时候,若 C h a n n e l 没有数据可以 给Con s u m e r,这时 Con s u m e r 在 C
16、h a n n e l 可以传送数据 之前,保持等待状态。当程序存在有多个 P r o d u c e r 与 Con s u m e r 时,为了使处理不互相干涉,C h a n n e l 也要进 行共享互斥。所以C h a n n e l 介于P r o d u c e r、Con s u m e r 之 间,起到传递数据的作用。图2 是生产者 消费者模式的类图。图 2 生产者 消费者类图 3 线程调用实例 在多线程设计中,通常会有多个线程调用一个实 维普资讯 http:/ 计算机与现代化 2 0 0 6年第 1 1 期 例,来实现所需要的功能和结果。这就是线程调用实 例的设计模式,可以
17、采用三种设计方法实现线程调用。第一种设计方法。每次用户调用该实例时,都启动新的线 程,并且将需要进行的工作。交给这个新的线程去执行,这样用 户线程可以继续执行下一个操作。能够有效地提高程序的响应 速度。缺点就是在每次提出调用实例请求后。启动线程会花费 很长时间。第二种设计方法。是弥补第一种方法中启动线程耗费时间 而建立的。在用户提出调用实例之前,程序先启动一些用来进 行处理的线程,并将请求的实例传给这些线程。这样就不需要 每次都重新启动新的线程。为了比较这两种方法的差别,建立程序来执行同样的实 例(同时启动3个线程)。在相同的处理时间内(3 o秒),比较两 种设计方法之间线程运行次数。如图 3
18、、图4 所示。第一种设计方法 第一种设计方法 第一种设计方法 第一种设计方法 第一种设计方法 第一种设计方法 第一种设计方法 第一种设计方法 第一种设计方法 在 2 9 8 2 8毫秒时执行第 在 2 9 8 2 8毫秒时执行第 在 2 粥2 8毫秒时执行第 在 2 9 8 2 8毫秒时执行第 在 2 9 8 2 8毫秒时执行第 在 2 9 8 4 4毫秒时执行第 在 2 9 8 4 4毫秒时执行第 在 2 9 8 4 4毫秒时执行第 在 2 9 8 4 4毫秒时执行第 1 5 9 9项工作 1 6 o 1 项 工作 1 6 0 3 项工作 1 6 Q 5 项工作 1 6 0 r 7 项 工作
19、 1 6 0 9 项工作 1 6 1 1 项工作 1 6 1 3 项工作 1 6 1 5 项 工作 D:、J A v A 图 3 第一种设计方法执行结果 第二种设计方法 第二种设计方法 第二种设计方法 第二种设计方法 第二种设计方法 第二种设计方法 第二种设计方法 第二种设计方法 D t j 在 2 9 9 5 3 毫秒时执行第 7 0 8 4 9 项工作 在 2 9 9 5 3毫秒时执行第 7 0 8 5 0 项工作 在 2 9 9 5 3毫秒时执行第 7 0 8 5 1 项工作 在 2 9 9 5 3毫秒时执行第7 0 8 4 3 项工作 在 2 9 9 5 3毫秒时执行第 7 0 8 4
20、 5 项工作 在 2 9 9 5 3毫秒时执行第 7 0 8 4 6项工作 在 2 9 9 5 3毫秒时执行第 7 085 2 项工作 在 2 9 9 5 3毫秒时执行第7 0 8 4 4项工作 图 4 第二种设计方法执行结果 从执行结果中可看出,第二种方法的执行效率比第一种 方法高。第三种设计方法,假设有一个实例方法执行起来需要一 些时间,为了提高响应时间,可以设计成不让执行该方法的线 程等待执行结果,而获取一张替代 的表示保证在一定时间内 完成的“提货单”。因为获取提货单不需要消耗时间,在这里。这个“提货单”就是 F u t u r e 参与者。获取 F u t u r e 参与者的线程。
21、会在所有任务请求完成后,再去获取执行结果。如果已经有 执行结果,就可以马上拿到数据。如果执行结果还没准备好。则继续等待直到执行结果出现为止。实现方式是先建立一个与处理结果相同的 F u t u r e 参与者。在运行程序开始时,先把F u t u r e 参与者当作返回值返回。处理的 结果事后再设置给F u t u r e 参与者。经过运行,用户就可以在适当 的时机。通过 F u t u r e 参与者,获取(等待)处理的 结果。,F u t u r e j a v a ,c l a s s F u t u r e p ri v a t e R e a l Da ta t t l D uta=
22、n u l l;p r i v a t e b o o l e a n r e a d y f a l s e;p u b l i c s y n c h r o n i z e d v o i d R e a l D a t a(R e a l D a t a r e a 1D a t a)i f(r e a d y)r e t u r n;中断 t h i s r e a l D a t a=r e a l D a t a|赋予真实数据 t h i s r e a d y=t r u e;n o y A U();唤醒所有线程 p ubl i c s y n c h r o n i z e
23、d S t r i C o n t e n t()获得执行结果 w h i l e(!r e a d y)t r y 异常处理 w a i t();e a t e h(I n t e r r u p t e d E x c e p t i o n e)l t u l l r e a l D a t a C o n t e n t();赋予执行结果 ,V i r t u a l D a t a j a v a ,p l i e i n t e d a c e V m u a 虚拟数据,也蠡 是 提觯”p ubli c a b s t r a c t S t r i n g Con t e n t
24、();定义抽象内容 F u tu r e i a v a 获得结果后可通过该接口传输 4 线程模式扩展 假定在单线程环境下运行的对象,在不修改使用 端的线程,同时也不能改变对象接口(A P I)前提下,需 要将这个对象放在多线程环境下执行。使用 j a v a 1 a n g T h r c a d L o c a l 类可以达到此 目的。i a v a ta n g 1 h _,lL o c a l 的实例可以想象成保管箱间,管理着多个对象。3 h le a d L o 类包含s e t 和斛 方法。T h r e a d Ia l 类的 s e t 方法,可以将参数所指定的 实例。存放到调
25、用 s e t 方法的线程所对应的存储空间。在 s e t 方法 中,会检查当前线程(即 T h r e a d c u r r e n t T h r e a d()的值)。自动以这个值作为键值存放实例。T h d【D c a l 类的 g e c 方法,可以取得调用 g e c 方法 线程所对应的实例。S e t 方法中的实例结果,就是现 在 t 方法中的返回值。如果从未使用过 s e t 方法,就会返回n u l l 值。图5 是该特殊情况下的程序设计 模式结构图。图5 特殊模式结构图(下转第 9 8页)维普资讯 http:/ 9 8 计算机与现代化 2 0 0 6年第 l 1 期 M
26、e r 砒 这样的方法调用,则根据方法名用二分法去查找二维数组,找到对应的方法地址,然后直接调用即可。如果是“E v e n t”关键 字,则把它添加到个事件的 链表中,链表的节点记录以下信息:当前的二进制文件的指针,事件的名称和类型。当引擎把二进制文件读完一遍后,随即进入一个无限 循环,每隔一段时间去调用键盘库函数,如果有键值,则根据 当前的屏幕号和键值,去事件链表中查找是否有匹配的事件 处理代码。如能找到,则执行相应事件的处理代码;如找不 到,则把键值丢弃即可。通过串口连线,把这个二进制脚本从 P C 机上传送到嵌 入式平台上,由 u C l l n u x上的引擎解释执行。当脚本在 P
27、C上 模拟执行都正确时,在嵌入式平台上也就能正常运行了。2 3 交互式开发脚本程序 G U I 开发时最好能做到交互式开发,达到“所见 即所得”的效果,因此我们也可以仿照 V is u a l B a s i c 开 发环境中的可视化设计器,做一个简单的界面设计 器,让用户在设计器里摆放菜单和文本输入框等控 件,定义各个响应事件的脚本,然后综合起来自动生 成脚本文件。因为脚本执行引擎在 P C上就能工作,所以设计的脚本程序不用下载到手持设备里调试,在 P C上马上就能看到运行结果,等 P C 上的运行结果都 正确后再下载到手持设备里,这样不但方便了用户,而且提高了工作效率。3 结束语 嵌入式操
28、作系统的诞生,使嵌入式系统的开发进 入了一个新的阶段,与传统的单片机开发模式有一定 的区别。本文基于 u C li n u x 操作系统,介绍实现一个 手持设备的G U I 应用系统,由点到面叙述了系统的设 计要点。由于系统层次结构比较清晰,同时引入脚本 执行引擎,让用户使用脚本开发应用程序,符合现代 编程的思维模式,所以这个应用系统可以作为一个通 用架构,对于在嵌入式设备中,特别是基于嵌入式 L in u x 的开发应用有重要的参考意义。参考文献:1 邹思轶 嵌入式 L i n u x 设计与应用 M 北京:清华大学 出版社,2 0 0 2 2 J e a n J L a b r o s s
29、 e 嵌入式系统构件 M 北京:机械工业 出版社,2 0 0 2 3 刘轶,韦宏利,伍卫国 用于手持设备的嵌入式计算机 系统设计 J 微电子学与计算机,2 O O 4,2 1(1 0)4 李平,母晓风 一种关于实时嵌入式系统 G U I 解决方案 J 长春理工大学学报,2 0 0 5,2 8(1)5 吴峰,王自 强 基于 F r a m e B u ff e r 的嵌入式 G U I 系统设计 J 计算机应用与软件,2 0 0 5,2 2(3)6 韩会敏,王忠仁,邓文红 用于手持设备人机交互应用 的架构设计与开发框架 J 中国民航飞行学院学报,2 0 0 5,1 6(4)(上接第9 4页)程序
30、运行结束后,都要中止线程的执 行。多线程设计中的终止线程运行模式是:首先送出 “终止请求”,此时这个线程并不会马上结束,而会进 行必要的刷新工作。此时,线程仍然运行,但不是平 常的操作,而是进行终止处理,直到终止处理结束后,才真正结束线程。以下是该过程的代码段:p u b l i c fi n a l v o idri m()t r y w h i l e(!s h u t d o w n R e q u e s t e d)未终止请求 Wo r k();线程执行 c a t c h(ht e r mp t e d E x c e p t i o n e)处理异常 f in a l l y s
31、 h u t D o w n();终止运行 这种终止线程执行的方式,能够很好地结束线程 操作。其优势表现在:(1)安全性:即使收到终止请求,也不会马上结束线程,而 是提出表示收到终止请求的 s h u t d o w n R e q u e s t e d 标识。(2)生命性:当收到终止请求时,会中断可以中断的 w a i t 方法。(3)响应性:当收到终止请求后,会中断可以中断的 s l e e p 方法,尽快前进到终止处理。5 结束语 本文对 J a v a多线程设计模式进行归类研究,总 结各种模式解决方法。在实际的软件开发中,应用这 些模式进行程序设计,能够避免数据不一致性、数据 冲突、
32、死锁等并发问题,缩短并行任务的执行时间,提 高运行效率,得到高质量的 J a v a 多线程并发程序。参考文献:1 日 结城浩 J a v a 多线程设计模式 M 博硕文化译 北 京:中国铁道出版社,2 0 0 5 3 2 6 3 4 0 2 徐绍忠,王乘,刘小虎 J a v a 并发机制研究 J 计算机 工程,2 0 0 2(4)3 美 H w a n g K A d v a n c e d C o m p u t e r A r c h i t e c t u r e P a r a l l e l i s m S c a l a b i l i t y P r o g r m n m i l i t y M M c G r a w-H i l l,1 9 9 5 4 E c k e l B T h i n k i n g i n J a v a(2 E d i t i o n)M P r e n t i c e-H a l l,2 00 0 维普资讯 http:/