查看豆瓣读书

第六章 任务执行

大多数并发应用程序是围绕执行任务进行管理的。设计任务时,要为任务设计一个清晰的任务边界,并配合一个明确的任务执行策略。任务最好是独立的,因为这会提高并发度。大多数服务器应用程序都选择了下面这个自然的任务边界:单个客户请求。
任务时逻辑上的工作单元,线程是使任务异步执行的机制。
应用程序内部的任务调度,存在多种可能的调度策略:
其中,最简单的策略是在单一的线程中顺序的执行任务。但它的吞吐量和响应性很差,一般只在特殊情况下使用:任务的数量很少但生命周期很长时,或者服务器只服务于唯一的用户时,服务器在同一时间内只需同时处理一个请求。
每任务一个线程(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框架可以简化构建可取消的任务和服务。

《Java并发编程实战》学习笔记 任务执行和取消关闭的更多相关文章

  1. java并发编程实战学习(3)--基础构建模块

    转自:java并发编程实战 5.3阻塞队列和生产者-消费者模式 BlockingQueue阻塞队列提供可阻塞的put和take方法,以及支持定时的offer和poll方法.如果队列已经满了,那么put ...

  2. 《java并发编程实战》笔记

    <java并发编程实战>这本书配合并发编程网中的并发系列文章一起看,效果会好很多. 并发系列的文章链接为:  Java并发性和多线程介绍目录 建议: <java并发编程实战>第 ...

  3. Java并发编程实战 读书笔记(一)

    最近在看多线程经典书籍Java并发变成实战,很多概念有疑惑,虽然工作中很少用到多线程,但觉得还是自己太弱了.加油.记一些随笔.下面简单介绍一下线程. 一  线程与进程   进程与线程的解释   个人觉 ...

  4. Java并发编程实战 读书笔记(二)

    关于发布和逸出 并发编程实践中,this引用逃逸("this"escape)是指对象还没有构造完成,它的this引用就被发布出去了.这是危及到线程安全的,因为其他线程有可能通过这个 ...

  5. Java并发编程实践(读书笔记) 任务执行(未完)

    任务的定义 大多数并发程序都是围绕任务进行管理的.任务就是抽象和离散的工作单元.   任务的执行策略 1.顺序的执行任务 这种策略的特点是一般只有按顺序处理到来的任务.一次只能处理一个任务,后来其它任 ...

  6. 《Java并发编程实战》笔记-非阻塞算法

    如果在某种算法中,一个线程的失败或挂起不会导致其他线程也失败和挂起,那么这种算法就被称为非阻塞算法.如果在算法的每个步骤中都存在某个线程能够执行下去,那么这种算法也被称为无锁(Lock-Free)算法 ...

  7. 《Java并发编程的艺术》第4章 Java并发编程基础 ——学习笔记

    参考https://www.cnblogs.com/lilinzhiyu/p/8086235.html 4.1 线程简介 进程:操作系统在运行一个程序时,会为其创建一个进程. 线程:是进程的一个执行单 ...

  8. 《Java并发编程实战》笔记-Happens-Before规则

    Happens-Before规则 程序顺序规则.如果程序中操作A在操作B之前,那么在线程中A操作将在B操作之前执行. 监视器锁规则.在监视器锁上的解锁操作必须在同一个监视器锁上的加锁操作之前执行. v ...

  9. 《Java并发编程实战》笔记-状态依赖方法的标准形式

    void stateDependentMethod() throws InterruptedException { //必须通过一个锁来保护条件谓词 synchronized(lock) { whil ...

随机推荐

  1. -----------------------------------项目中整理的非常有用的PHP函数库(一)-----------------------------------------------------

    1.PHP加密解密 PHP加密和解密函数可以用来加密一些有用的字符串存放在数据库里,并且通过可逆解密字符串,该函数使用了base64和MD5加密和解密. function encryptDecrypt ...

  2. php网站验证码的生成

    <?php header("Content-type:text/html;charset=utf-8"); header("Content-type:image/p ...

  3. 织梦DedeCms去掉栏目页面包屑导航最后的分隔符“>”

    织梦DedeCms的面包屑导航调用标签{dede:field name=’position’ /},在栏目页里调用的面包屑导航,最后会出现分割符号“>”,如:主页 > DedeCms 模板 ...

  4. vim设置语法高亮

    在vim安装目录中的_vimrc修改,加上以下的代码. set nu! colorscheme desert      syntax enable      syntax on

  5. 怎样把excel一列分成多列

    1,选定要分列的列. 2,点击“数据”-“分列”. 3,在选项栏中设置如图 4,选择分隔符 4,看,分开了吧!

  6. ECMAScript6-下一代Javascript标准

    介绍 ECMAScript6是下一代Javascript标准,这个标准将在2015年6月得到批准.ES6是Javascript的一个重大的更新,并且是自2009年发布ES5以来的第一次更新. 它将会在 ...

  7. HDOJ 1083 Courses

    Hopcroft-Karp算法模板 Courses Time Limit: 20000/10000 MS (Java/Others)    Memory Limit: 65536/32768 K (J ...

  8. js实现table排序-sortable.js

    方案一.引用sortable.js包 /* <th class="thcss" style="width: 40px;" onclick="so ...

  9. [Effective JavaScript 笔记] 第6条:了解分号插入的局限

    分号可以省略 js可以在语句结束不强制加分号.(建议还是添加,不添加分号往往会出现不易发现的BUG) function Point(x,y){ this.x=x||0; this.y=y||0; } ...

  10. 学号160809212姓名田京诚C语言程序设计实验2选择结构程序设计

    编写一个C程序,输入3个数,并按由大到小的顺序输出. 1 #include <stdio.h> void main(){ int a,b,c,t; printf("请输入三个整数 ...