诸如 Web 服务器、数据库服务器、文件服务器或邮件服务器之类的许多服务器应用E序都面向处理来自某些远E来源的大量短小的Q务。请求以某种方式到达服务器,q种?式可能是通过|络协议Q例?HTTP、FTP ?POPQ、通过 JMS 队列或者可能通过轮询数据库。不请求如何到达,服务器应用程序中l常出现的情冉|Q单个Q务处理的旉很短而请求的数目却是巨大的?/p>
构徏服务器应用程序的一个过于简单的模型应该是:每当一个请求到辑ְ创徏一个新U程Q然后在新线E中求服务。实际上Q对于原型开发这U方法工作得?好,但如果试N|以q种方式q行的服务器应用E序Q那么这U方法的严重不很明显。每个请求对应一个线E(thread-per-requestQ方 法的不之一是:为每个请求创Z个新U程的开销很大Qؓ每个h创徏新线E的服务器在创徏和销毁线E上p的时间和消耗的pȝ资源要比花在处理实际的用 戯求的旉和资源更多?/p>
除了创徏和销毁线E的开销之外Q活动的U程也消耗系l资源。在一?JVM 里创建太多的U程可能会导致系l由于过度消耗内存而用完内存或“切换q度”。ؓ了防止资源不I服务器应用程序需要一些办法来限制Ml定时刻处理的请求数目?/p>
U程池ؓU程生命周期开销问题和资源不问题提供了解决Ҏ(gu)。通过对多个Q务重用线E,U程创徏的开销被分摊到了多个Q务上。其好处是,因ؓ在请求到达时 U程已经存在Q所以无意中也消除了U程创徏所带来的gq。这P可以立即ؓh服务Q应用E序响应更快。而且Q通过适当地调整线E池中的U程数目Q也 是当请求的数目过某个阈值时Q就强制其它M新到的请求一直等待,直到获得一个线E来处理为止Q从而可以防止资源不?/p>
虽然U程池是构徏多线E应用程序的强大机制Q但使用它ƈ不是没有风险的。用U程池构建的应用E序Ҏ(gu)遭受M其它多线E应用程序容易遭受的所有ƈ发风险,诸如同步错误和死锁,它还Ҏ(gu)遭受特定于线E池的少数其它风险,诸如与池有关的死锁、资源不_U程泄漏?/p>
M多线E应用程序都有死锁风险。当一l进E或U程中的每一个都在等待一个只有该l中另一个进E才能引L事gӞ我们pq组q程或线E? 死锁了。死锁的最单情形是Q线E?A 持有对象 X 的独占锁Qƈ且在{待对象 Y 的锁Q而线E?B 持有对象 Y 的独占锁Q却在等待对?X 的锁。除非有某种Ҏ(gu)来打破对锁的{待QJava 锁定不支持这U方法)Q否则死锁的U程永q等下去?
虽然M多线E程序中都有死锁的风险,但线E池却引入了另一U死锁可能,在那U情况下Q所有池U程都在执行已阻塞的{待队列中另一d的执行结果的dQ?但这一d却因为没有未被占用的U程而不能运行。当U程池被用来实现涉及许多交互对象的模拟,被模拟的对象可以怺发送查询,q些查询接下来作为排队的?务执行,查询对象又同步等待着响应Ӟ会发生这U情c?/p>
U程池的一个优点在于:相对于其它替代调度机Ӟ有些我们已经讨论q)而言Q它们通常执行得很好。但只有恰当地调整了U程池大时才是q样的。线E消耗包括内存和其它pȝ资源在内的大量资源。除? Thread
对象所需的内存之外,每个U程都需要两个可能很大的执行调用堆栈。除此以外,JVM 可能会ؓ每个 Java U程创徏一个本机线E,q些本机U程消耗额外的pȝ资源。最后,虽然U程之间切换的调度开销很小Q但如果有很多线E,环境切换也可能严重地影响E序的性能?
如果U程池太大,那么被那些线E消耗的资源可能严重地媄响系l性能。在U程之间q行切换会费旉Q而且使用出比?zhn)实际需要的U程可能会引赯源匮?问题Q因为池U程正在消耗一些资源,而这些资源可能会被其它Q务更有效地利用。除了线E自w所使用的资源以外,服务h时所做的工作可能需要其它资源,??JDBC q接、套接字或文件。这些也都是有限资源Q有太多的ƈ发请求也可能引v失效Q例如不能分?JDBC q接?/p>
U程池和其它排队机制依靠使用 wait()
? notify()
Ҏ(gu)Q这两个Ҏ(gu)都难于用。如果编码不正确Q那么可能丢失通知Q导致线E保持空闲状态,管队列中有工作要处理。用这些方法时Q必L外小心;即便是专家也可能在它们上面出错。而最好用现有的、已l知道能工作的实玎ͼ例如在下面的 无须~写(zhn)自q?/a>中讨论的 util.concurrent
包?
各种cd的线E池中一个严重的风险是线E泄漏,当从池中除去一个线E以执行一Q务,而在d完成后该U程却没有返回池Ӟ会发生这U情c发生线E泄漏的一U情形出现在d抛出一? RuntimeException
或一? Error
时。如果池cL有捕捉到它们Q那么线E只会退线E池的大将会永久减一个。当q种情况发生的次数够多ӞU程池最l就为空Q而且pȝ停止,因ؓ没有可用的线E来处理d?
有些d可能会永q等待某些资源或来自用户的输入,而这些资源又不能保证变得可用Q用户可能也已经回家了,诸如此类的Q务会怹停止Q而这些停止的d?会引起和U程泄漏同样的问题。如果某个线E被q样一个Q务永久地消耗着Q那么它实际上就被从池除M。对于这LdQ应该要么只l予它们自己的线E,?么只让它们等待有限的旉?/p>
仅仅是请求就压垮了服务器Q这U情冉|可能的。在q种情Ş下,我们可能不想每个到来的h都排队到我们的工作队列,因ؓ排在队列中等待执行的d可能?消耗太多的pȝ资源q引赯源缺乏。在q种情Ş下决定如何做取决于?zhn)自己Q在某些情况下,(zhn)可以简单地抛弃hQ依靠更高别的协议E后重试hQ?zhn)也?以用一个指出服务器暂时很忙的响应来拒绝h?/p>
只要(zhn)遵循几条简单的准则Q线E池可以成ؓ构徏服务器应用程序的极其有效的方法: