《Java并发编程实战》学习笔记 任务执行和取消关闭

简介:

第六章 任务执行

大多数并发应用程序是围绕执行任务进行管理的。设计任务时,要为任务设计一个清晰的任务边界,并配合一个明确的任务执行策略。任务最好是独立的,因为这会提高并发度。大多数服务器应用程序都选择了下面这个自然的任务边界:单个客户请求。
任务时逻辑上的工作单元,线程是使任务异步执行的机制。
应用程序内部的任务调度,存在多种可能的调度策略:
其中,最简单的策略是在单一的线程中顺序的执行任务。但它的吞吐量和响应性很差,一般只在特殊情况下使用:任务的数量很少但生命周期很长时,或者服务器只服务于唯一的用户时,服务器在同一时间内只需同时处理一个请求。
每任务一个线程(thread-per-task)。在中等强度的负载下,“每任务一个线程”的方法是对顺序化执行的良好改进。但它存在一些实际的缺陷,因为它会无限制的创建线程,创建/关闭线程是需要开销的,同时线程还会消耗系统资源,而且会影响稳定性。所以应该限制可创建线程的数目。
使用线程池——Executor框架。如同有界队列,Executor可以防止应用程序过载而耗尽资源,而且Executor是基于生产者-消费者模式的,可以分离任务提交和任务执行。如果要在你的程序中实现一个生产者-消费者的设计,使用Executor通常是最简单的方式。
使用Executor的一个优点是:要改变程序的运行,只要改变Executor的实现就行,也就是任务的执行,不需要动任务的提交,而且Executor的实现是放在一个地方的,但任务的提交则是扩散到整个程序中。
执行策略是资源管理工具,最佳策略取决于可用的计算资源和你对服务质量的需求。一个执行策略指明了任务执行的“what,where,when,how”几个因素,具体包括:
任务在什么(what)线程中执行?
任务以什么(what)顺序执行(FIFO,LIFO,优先级)?
可以有多少个(how many)任务并发执行?
可以有多少个(how many)任务进入等待执行队列?
如果系统过载,需要放弃一个任务,应该挑选哪一个(which)任务?另外,如何(how)通知应用程序知道这一切呢?
在一个任务的执行前与结束后,应该做什么(what)处理?
Executor的生命周期
Executor有三种状态:运行、关闭、终止。创建后的初始状态是运行状态,shutdown()方法会启动一个平缓的关闭过程,shutdownNow()方法会启动一个强制的关闭过程。在关闭后提交到Executor中的任务,会被被拒执行处理器(RejectedExecutionHandler)处理(可能只是简单的放弃)。一旦所有的任务全部完成后,Executor回转入终止状态,可以调用awaitTermination等待,或者isTerminated判断。
可以使用scheduledThreadPoolExecutor代替Timer使用,Timer存在一些缺陷:Timer只创建唯一的线程来执行所有timer任务;Timer抛出的未检查的异常会终止timer线程,而且Timer也不会再重新恢复线程的执行了。
Executor框架让制定一个执行策略变得简单,不过想要使用Executor,你还必须能够将你的任务描述为Runnable。
Runnable、Callable、Future比较
Runnable是执行任务的抽象,但它的run方法不能返回一个值或者跑出受检查的异常;Callable是更佳的抽象,它的run方法有返回值,并能抛出异常;而Future提供了相关的方法来获得任务的结果、取消任务以及检验任务是否已经完成还是被取消。
Executor的所有submit方法都返回一个Future,用它可以重新获得任务执行的结果,或者取消任务。除此之外,在Java 6中,ExecutorService的所有实现都可以重写newTaskFor方法,把Callable封装成Future。
将程序的任务量分配到不同的任务中:当存在大量的相互独立、同类的能够并发处理的任务时,性能才能真正的提升;否则,性能提升的相当少,甚至降低性能。
当有一批任务需要Executor处理时,使用completionService更方便,而且还可以使用take方法,获取完成的任务(可以没完成一个取一个,提高并发)。如果不需要边完成边去结果的话,处理批任务还可以使用Executor.InvokeAll方法。
总结
围绕任务的执行来构造应用程序,可以简化开发,便于同步。Executor框架有助于分离任务的提交和任务的执行策略,同时还支持很多不同类型的执行策略。每当你要为执行任务而创建线程时,可以考虑使用Executor。为了最大化效益,在把应用程序分解为不同的任务时,你必须确定一个合乎情理的任务边界。在一些应用程序中,存在明显的工作良好的任务边界,然而还有一些程序,你需要作进一步的分析,以揭示更多可行的并发。


第七章 取消和关闭

任务取消:当外部代码能够在活动自然完成之前,把它更改为完成状态,那么这个活动被称为可取消的。活动取消的原因:用户请求取消、限时活动、应用程序事件、错误、关闭。
Java没有提供任何机制,来安全的强迫线程停止手头的工作。它提供了中断——一个协作机制,是一个线程能够要求另一个线程停止当前的工作。任务和服务可以这样编码:当要求它们停止时,它们首先清除当前进程中的工作,然后再终止。因而需要一个取消策略。
取消策略,取消的how、when、what:其他代码如何请求取消该任务,任务在什么时候检查取消的请求是否到达,响应取消请求的任务中应有的行为。
在API和语言规范中,并没有把中断与任何取消的语意绑定起来,但是,实际上,使用中断来处理取消之外的任何事情都是不明智的,并且很难支撑起更大的应用。
调用interrupt并不意味着必然停止目标线程正在进行的工作;它仅仅传递了请求中断的消息。我们对中断本身最好的理解应该是:它并不会真正中断一个正在运行的线程;它仅仅发出中断请求,线程自己会在下一个方便的时刻中断(取消点)。
中断通常是实现取消最明智的选择。
因为每一个线程都有其自己的中断策略,所以你不应该中断线程,除非你知道中断对这个线程意味着什么。
调用可中断的阻塞函数时,如Thread.sleep、BlockingQueue.put,有两种处理InterruptedException的实用策略:
传递异常(很可能发生在清除特定任务后),使你的方法也成为可中断的阻塞方法;
或者恢复中断状态,从而上层调用栈中的代码能够对其进行处理。
只有实现了线程中断策略的代码才可以接受中断请求。一般性的任务和程序库代码不应该接受中断请求。
当Future.get抛出InterruptedException或TimeoutException时,如果你知道不再需要结果时,就可以调用Future.cancel来取消任务了。
被不可中断活动阻塞的线程,我们可以用类似于中断的技术停止它们,但这更需要明确线程阻塞的原因。
对于拥有线程的服务,只要服务的生存时间大于创建线程的方法的生存时间,就需要提供生命周期方法。
生产者-消费者服务关闭的方法:
自己提供生命周期方法,生产者原子的添加工作,比较难。
使用ExecutorService类:封装ExecutorService,在内部代码中调用ExecutorService的生命周期方法——shutdown、shutdownNow。在非生产者-消费者中也适用。
使用毒丸:一个可识别的对象,置于队列中,意味着“当你得到它时,停止一切工作”。要停止服务时,中断生产者——生产者的中断处理中向每一个消费者添加一个毒丸,消费者碰到毒丸,停止工作。
如果一个方法需要处理一批任务,并在所有任务结束前不会返回,那么他可以通过使用私有的Executor来简化服务的生命周期管理,其中Executor的生命限定在该方法中。
shutdownNow的局限性:它试图取消正在进行的任务,并返回那些等待执行的任务的清单,但是我们没法找出那些已经开始执行、却没有结束的任务,这需要自己处理。
在一个长时间运行的应用程序中,所有的程序都要给未捕获异常设置一个处理器,这个处理器至少要将异常信息记入日志中。
线程分为两种:普通线程和守护线程。两者的区别是:当一个线程退出时,所有仍然存在的守护线程都会被抛弃——不会执行finally块,也不会释放栈——JVM直接退出。
管理资源避免使用finalizer。在大多数情况下,使用finally块和显式close方法结合来管理资源,会比使用finalizer起到更好的作用。
总结
任务、线程、服务以及应用程序在生命周期结束时的问题,可能会导致向它们引入复杂的设计和实现。Java没有提供具有明显优势的机制来取消活动或者终结线程。它提供了协作的中断机制,能够用来帮助取消,但是这将取决你如何构建取消的协议,并是否能一致的使用该协议。使用FutureTask和Executor框架可以简化构建可取消的任务和服务。


本文转自邴越博客园博客,原文链接:http://www.cnblogs.com/binyue/p/3715671.html,如需转载请自行联系原作者
相关文章
|
17天前
|
安全 Java 开发者
深入理解Java并发编程:线程安全与性能优化
【4月更文挑战第9天】本文将深入探讨Java并发编程的核心概念,包括线程安全和性能优化。我们将详细解析Java中的同步机制,包括synchronized关键字、Lock接口以及并发集合等,并探讨它们如何影响程序的性能。此外,我们还将讨论Java内存模型,以及它如何影响并发程序的行为。最后,我们将提供一些实用的并发编程技巧和最佳实践,帮助开发者编写出既线程安全又高效的Java程序。
23 3
|
3天前
|
安全 Java 调度
Java线程:深入理解与实战应用
Java线程:深入理解与实战应用
21 0
|
15天前
|
安全 算法 Java
深入理解Java并发编程:线程安全与性能优化
【4月更文挑战第11天】 在Java中,高效的并发编程是提升应用性能和响应能力的关键。本文将探讨Java并发的核心概念,包括线程安全、锁机制、线程池以及并发集合等,同时提供实用的编程技巧和最佳实践,帮助开发者在保证线程安全的前提下,优化程序性能。我们将通过分析常见的并发问题,如竞态条件、死锁,以及如何利用现代Java并发工具来避免这些问题,从而构建更加健壮和高效的多线程应用程序。
|
1天前
|
消息中间件 缓存 NoSQL
Java多线程实战-CompletableFuture异步编程优化查询接口响应速度
Java多线程实战-CompletableFuture异步编程优化查询接口响应速度
|
1天前
|
存储 安全 Java
Java并发编程中的高效数据结构:ConcurrentHashMap解析
【4月更文挑战第25天】在多线程环境下,高效的数据访问和管理是至关重要的。Java提供了多种并发集合来处理这种情境,其中ConcurrentHashMap是最广泛使用的一个。本文将深入分析ConcurrentHashMap的内部工作原理、性能特点以及它如何在保证线程安全的同时提供高并发性,最后将展示其在实际开发中的应用示例。
|
2天前
|
Java API 调度
[AIGC] 深入理解Java并发编程:从入门到进阶
[AIGC] 深入理解Java并发编程:从入门到进阶
|
2天前
|
Dubbo Java 应用服务中间件
Java从入门到精通:3.2.2分布式与并发编程——了解分布式系统的基本概念,学习使用Dubbo、Spring Cloud等分布式框架
Java从入门到精通:3.2.2分布式与并发编程——了解分布式系统的基本概念,学习使用Dubbo、Spring Cloud等分布式框架
|
3天前
|
Java
Java中的并发编程:理解和应用线程池
【4月更文挑战第23天】在现代的Java应用程序中,性能和资源的有效利用已经成为了一个重要的考量因素。并发编程是提高应用程序性能的关键手段之一,而线程池则是实现高效并发的重要工具。本文将深入探讨Java中的线程池,包括其基本原理、优势、以及如何在实际开发中有效地使用线程池。我们将通过实例和代码片段,帮助读者理解线程池的概念,并学习如何在Java应用中合理地使用线程池。
|
7天前
|
存储 Java 数据库连接
java DDD 领域驱动设计思想的概念与实战
【4月更文挑战第19天】在Java开发中,领域驱动设计(Domain-Driven Design, DDD) 是一种软件设计方法论,强调以领域模型为中心的软件开发。这种方法通过丰富的领域模型来捕捉业务领域的复杂性,并通过软件满足核心业务需求。领域驱动设计不仅是一种技术策略,而且还是一种与业务专家紧密合作的思维方式
28 2
|
7天前
|
安全 Java 开发者
Java并发编程:深入理解Synchronized关键字
【4月更文挑战第19天】 在Java多线程编程中,为了确保数据的一致性和线程安全,我们经常需要使用到同步机制。其中,`synchronized`关键字是最为常见的一种方式,它能够保证在同一时刻只有一个线程可以访问某个对象的特定代码段。本文将深入探讨`synchronized`关键字的原理、用法以及性能影响,并通过具体示例来展示如何在Java程序中有效地应用这一技术。