线程的优点和风险

1、线程的优点
恰当地使用线程时,可以降低开发和维护的开销,并且能够提高复杂应用的性能。线程通过把异步的工作流程转化为普遍存在的顺序流程,使程序模拟人类工作和交互变得更容易了。另一方面,它们可以把复杂、难以理解的代码转化为直接、简洁的代码,这样更容易读写及维护。
线程在GUI应用程序中是非常有用的,可以来改进用户接口的响应性,并且在服务器应用中,用于提高资源的利用率和吞吐量。它们也可以简化JVM的实现——垃圾收集器(garbage collector)通常运行于一个或多个持续工作的线程之间。大部分至关重要的Java应用都依赖于线程,某种程度上是因为它们的组织结构需要这样。
1、1 使用多处理器
多处理器系统以往比较昂贵、稀少,只用于大的数据中心和科学计算设备。如今,多处理器系统已经比较便宜,数量也增多了;即使低端服务器和中端的桌面系统也常常采用多处理器。这个趋势只会逐渐增加;因为处理器很难再提高它的时钟频率,取而代之的是,处理器厂商会在一块芯片上放置更多的处理器内核。所有主要的芯片制造商都开始了这种转变,并且我们已经显著地看到机器中处理器数量的增加。
因为程序调度的基本单元是线程,一个单线程应用程序一次只能运行在一个处理器上。在双处理器系统中,一个单线程程序,放弃了其中一半的空闲CPU资源;在拥有100个处理器的系统中,这个单线程程序放弃了99%的资源。从另一方面来看,拥有多个活跃线程的程序可以同时在多处理器上运行。在设计良好的情况下,多线程程序通过更有效的利用空闲处理器资源,来提高吞吐量。
使用多线程也可以帮助我们在单线程系统中实现更佳的吞吐量。如果一个程序是单线程的,这个处理器在等待一个同步I/O操作完成的时候,仍然是空闲的。在一个多线程程序中,当第一个线程I/O结束的同时,另外一个线程也可以运行,这样就使得程序在遇到I/O阻塞的时候仍然有进展。(这就像在等待水烧开的时间里面读报纸,优于等待水开之后再去读报。)
1、2 模型的简化
当你需要完成的任务时统一类型(修改12个bug)的时候,掌控你的时间通常比完成多种类型的任务要容易(修改bug,面试系统管理员的替代候选人,完成你的团队的效率评估,为你下周的演讲制作幻灯片)。当你只有一种任务的时候,你可以从这堆任务的第一个开始,逐一去做,知道将它们全部完成(或者你自己精疲力尽);你不需要花精力去思考接下来去执行哪个任务。另一方面,管理多重优先级和截止日期,还要在各任务之间切换,这通常会带来一些成本开销。
对于软件来说也是同样的道理:相对于需要同时管理多重类型的任务,一个顺序处理相同类型任务的程序,写起来更简单,更少出错,也更容易测试。在模拟的情况下,为每一个类型的任务分配一个线程,或者为每一个元素分配一个线程,提供理想上的顺序,并且这样做可以把域逻辑(domain logic)与时序调度的细节隔离开来,进行相互交替的操作,进行异步I/O,以及等待资源。一个复杂、异步的流程可以被分解为一系列更简单的同步流程,它们中每一个在相互独立的线程中运行,只有在特定的同步点才进行彼此的交互。
这些优点通常被一些框架(framework)所使用,比如Servlet或者远程方法调用(RMI,Remote Method Invocation)。这些框架需要处理请求管理,线程创建和负载均衡的细节,与此同时还要处理不同部分转发到合适的组件的相应流程状态中区。Servlet的开发者不需要担心容器究竟同时正在处理多少个请求,或者Socket的输入输出流是否阻塞;当一个Servlet的Service方法作为Web请求的相应被调用时,它可以同步地处理这些请求,就想它使一个单线程程序一样。这可以简化组件开发,并且可以使学习曲线变缓。
1、3 对异步事件的简单处理
一个服务器应用程序,接受来自多个远程客户端的连接,如果每一个连接服务器都为其分配一个线程,并允许使用同步I/O,这样的程序开发起来更容易。
如果程序在读取Socket时没有可用数据,那么read方法会被阻塞直到有数据可用。在一个单线程应用程序中,这不仅意味着处理相应的请求停止了,也意味着在线程阻塞期间对所有请求的处理都停止了。为了避免这样的问题,单线程服务器程序被迫使用了非阻塞I/O,这要比同步I/O复杂得多,也更容易出错。然而,如果每一个请求都拥有自己的线程,那么阻塞就不会影响到其他请求的处理了。
历史上,操作系统把一个进程能够创建的线程限制在相对较少的数量上,大约有几百个(甚至更少)。因此,操作系统未多元化的I/O开发了一些高效的机制,比如Unix的select和poll系统调用,为了访问这些机制,Java类库对非阻塞I/O提供了一组包(java.nio)。然而,今天的操作系统在支持更大数量的线程方面有了巨大的进步,这使得即使是在那些拥有许多客户的平台上,“每线程每客户(thread-per-client)”模型也是现实的。
1、4 用户界面的更佳相应性
GUI阴功程序过去通常为单线程的,这意味着你要么通过大量输入时间频繁地测试整个代码(这通常杂乱无章且麻烦),要么执行所有的应用代码,间接地贯穿整个主事件循环(main event loop)。如果从主事件循环中调用的代码执行的时间过长,那么直到代码执行完毕,用户界面看上去都是冻结的,因为在控制权返回到主事件循环之前,程序无法执行用户界面事件。
AWT和Swing工具集这样的现代GUI框架,用事件派发线程(event dispatch thread,EDT)取代了主事件循环,当一个用户界面事件发生时,比如按下一个按钮,事件线程会调用程序定义的时间处理器。大部分GUI框架都是单线程化的子系统,所以主事件循环的有效性仍然可以得到体现,但是它运行于它自身线程GUI toolkit的控制下,而并非受控于应用程序。
在事件发生的线程中如果只有短暂的任务,那么界面总能够作出相应,因为事件线程总能够及时有效地处理好用户的活动。然而,在事件线程中处理一个长期、耗时的任务,比如一个大文档的拼写检查,或者从网络上获取一个资源,这会削减相应的效率。如果用户在这个任务运行的时候发生了一个新的动作,事件线程能够开始处理甚至知晓这个动作将会被延迟很久。雪上加霜的情况是,不仅UI失去了相应,而且用户很可能不能取消这个不愉快的任务,即使程序提供了cancel按钮,因为事件线程正在忙碌工作,知道那个冗长的任务结束,才能够开始处理cancel按钮的按下事件!但是,如果让这个耗时的任务运行在单独的线程里,那么事件线程就能能够自由地处理UI事件,使之具有更好的响应能力。
2、线程的风险
Java对线程内置的支持是一把双刃剑。它通过提供语言和类库,以及一个规范的跨平台存储模型(这个规范的存储模型使得在Java中开发“一次开发,随处运行(write-once,run-anywhere)”的并发程序成为可能),简化了并发程序的开发。这样做同时还提高了开发人员的门槛,因为更多的程序需要使用线程。
2、1安全危险
2、2活跃度的危险
2、3性能危险
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值