并发编程实战学习笔记(四)——任务执行

约定不等于承诺〃 2022-09-26 15:50 257阅读 0赞

无限制创建线程的不足

  • 线程生命周期的开销非常高; 创建与销毁过程中都极其耗费资源
  • 资源消耗; 活跃的线程会消耗系统资源,尤其是内存。如果可运行的线程数量多于可用处理器的数量,那么有些线程将闲置,大量的线程也会竞争CPU时钟周期,这个时候再创建更多的线程反而会降低性能。
  • 稳定性。 线程数量有限制,内存消耗过多,可能会抛出OutOfMemoryError

Executor框架的内部机制

基于生产者-消费者模式,提交任务的操作相当于生产者(生成待完成的工作单元),执行任务的线程则相当于消费者(执行完成这些工作单元)。

使用线程池的优势

  • 通过重用现有的线程而不是创建新线程,可以在处理多个请求时分摊线程创建和销毁过程中产生的巨大开销。
  • 当请求到达时,工作线程通常已经存在,因此不会由于等待创建线程而延迟任务的执行,从而提高了响应性。
  • 通过适当调整足够多的线程以便使处理器保持忙碌状态。
  • 同时还可以防止过多的线程相互竞争资源而使应用程序耗尽内存或者失败。

使用线程池限制线程数量并不能完全解决资源耗尽的问题

尽管服务器不会因为创建过多的线程而失败,但在足够长的时间内,如果任务到达的速度总是超过任务执行的速度,那么服务器仍有可能(只是更不易)耗尽内存,因为等待执行的Runnable队列将不断增长,可以通过使用一个有界队列在Executor框架内部解决这个问题。

ExecutorService生命周期:运行、关闭和已终止

  • ExecutorService在初使化创建时处于运行状态。
  • Shutdown方法将执行平缓的关闭过程:不再接受新的任务,同时等待已经提交的任务执行完成——包括那些还未开始执行的任务。
  • ShutdownNow方法将执行粗暴的关闭过程:它尝试取消所有运行中的任务,并且不再启动队列中尚未开始执行的任务。

CompletionService并发计算并批量获取结果

它是在线程池基础上进行并发计算的进一步抽象,将Executor和BlockingQueue的功能融合在一起。你可以将Callable任务提交给它来执行,然后使用类似于队列操作的take和poll等方法来获得已完成的结果。而这些结果在完成时将被封装为Future。

一个更简单的实现方法是使用Executor的invokeAll方法,提交一组任务,并返回一组Future以获取结果。

为任务设置时限

  1. 要实现这个功能,可以由任务本身来管理它的限定时间,并且在超时后中止执行或取消任务。
  2. 可再次使用Future,如果一个限时的get方法抛出了TimeoutException,那么可以通过Future来取消任务。

发表评论

表情:
评论列表 (有 0 条评论,257人围观)

还没有评论,来说两句吧...

相关阅读