《java并发编程实战》读书笔记6--取消与关闭
第7章 取消与关闭
这章的主要内容是关于如何使任务和线程安全,快速,可靠的停止下来。
7.1 任务取消
在Java中没有一种安全的抢占方式来停止线程,但是可以使用一些协作机制,比如:
让素数生成器运行1秒后取消(并不会刚好在运行1秒后停止,因为在请求取消的时刻和run方法中循环执行下一次检查之间可能存在延迟):
-7.1.1 中断
上面的取消方法有个重要的问题是:如果任务中调用了一个阻塞方法,例如BlockingQueue.put,那么任务可能永远不会检查取消标志,因此永远不会结束。比如:
前面第五章曾提到,一些特殊的阻塞库的方法支持中断。线程中断是一种协作机制,线程可以通过这种机制来通知另一个线程,告诉它在合适的或者可能的情况下停止当前工作,并转而执行其他的工作。
阻塞方法库,如Thread.sleep和Object.wait等,都会检查线程何时中断,并且在发现中断时提前返回。
下面来解决之前BrokenPrimeProducer中永远检查不到标志位的问题:使用中断而不是boolean标志来请求取消
-7.1.2 中断策略
*对于非线程所有者的代码来说(例如,对于线程池而言,任何在线程池实现以外的代码),应该小心地保存中断状态,这样拥有线程的代码才能对中断作出响应。
*大多数可阻塞的库函数知识抛出interruptedException作为中断响应,尽快退出流程,并把中断信息传递给调用者,从而使调用栈中的上层代码可以采取进一步的操作。
*当检查到中断请求时,任务并不需要放弃所有的操作——它可以推迟处理中断请求,并直到某个更合适的时刻。因此需要记住中断请求,并在完成当前任务后抛出InterruptedException或者表示已收到中断请求。
-7.1.3 响应中断
对于一些不支持取消但仍可以调用中断的阻塞方法的操作,它们必须在循环中调用这些方法(interrput方法), 并在发现中断后重新尝试。在这种情况下应将中断状态保存在本地,并在返回前恢复状态而不是在捕获InterruptedException时恢复状态:
这部分有点看不明白。。。 先pass,以后再来钻研钻研
-7.1.4 示例:计时运行
给出了在指定时间内运行一个任意的Runnable的示例。在调用线程中运行任任务,并安排了一个取消任务,在运行指定的时间间隔后中断它。这解决了从任务中抛出为检查异常的问题,因为该异常会被timedRun的调用者捕获。下面的程序用到了ScheduledExecutorService。ScheduledExecutorService定时周期执行指定的任务
这是一种简单的方法,但却破坏了一下规则:在中断线程之前,应该了解它的中断策略。由于timedRun可以从任意一个线程调用,因此无法知道这个调用线程的中断策略(说到这里我好像把中断策略所针对的对象搞混淆了)。
在join方法返回后,它将检查任务中是否有异常抛出( task.rethrow() ) 如果有的话则会在timeRunde的线程中再次抛出异常。执行任务的线程拥有自己的执行策略,即使任务不响应中断,限时运行的方法(join)仍能返回到它的调用者。
join的不足:无法知道执行控制是因为线程正常退出而返回还是因为join超时而返回。
PS:这一小节看得有点云里雾里的,汉化质量实在是太垃圾了,哪天翻翻英文原版书,看看这块的解释。
-7.1.5 通过Future来实现取消
最后那句话是神马意思? 醉了,语文没学好的表示很蛋疼。
-7.1.6 处理不可中断的阻塞
在java的库中,许多可阻塞的方法都是通过提前返回或者抛出InterruptedException来响应中断请求的,从而使开发人员更容易构建出能响应取消请求的任务。然而,并非所有的可阻塞的方法或者阻塞机制都能响应中断,中断请求只能设置线程的中断状态,除此之外没有任何其他作用。对于那些由于执行不可中断操作而被阻塞的线程,可以使用类似于中断的手段来停止这些线程,但这要求我们必须知道线程阻塞的原因。
-7.1.7 采用newTaskFor来封装非标准的取消
newTaskFor是一个工厂方法,它将创建Future来代表任务,还能返回一个RunnableFuture接口,该接口扩展了Future和Runnable(并有FutureTask实现)。通过定制表示任务的Future可以改变Future.cancel的行为。那个程序来演示:
这部分看得我有点云里雾里的, 以后刷二周目的时候再来仔细分析分析。
7.2 停止基于线程的服务
*应用程序通常会创建拥有多个线程的服务
*应用程序可以拥有服务,服务可以拥有工作线程,但应用程序并不能拥有工作线程,因此应用程序不能直接停止工作线程。
-7.2.1 示例:日志服务
现在还需要实现一种终止日志线程的方法,从而避免使JVM无法正常关闭。take方法能响应中断,如果将日志线程修改为当捕获到InterruptedException时退出,那么只需中断日志线程就能停止服务。然而,如果只是使日志线程退出,那么还不是一种完备的关闭机制。这种直接关闭的做法会丢失哪些正在等待被写入到日志的信息,而且其他线程在调用log时将被阻塞。另一种关闭LogWriter的方法是:设置某个“已请求关闭”标志,以避免进一步提交日志信息:
在收到关闭请求后,消费者会把队列中的所有消息写入日志,并解除所有在调用log时阻塞的生产者(我怎么感觉这里书这里和代码对不上号。。。)。然而这个方法中存在着竞态条件问题,使得该方法并不可靠。向LogWriter添加可靠的取消操作:
-7.2.2 关闭ExecutorService
在复杂的程序中,通常会将ExecutorService封装在某个更高级别的服务中,并且该服务能提供其自己的生命周期方法。如:
-7.2.3 “毒丸”对象
另一种关闭生产者-消费者服务的方式是使用“毒丸”对象,当得到这个对象时立即停止。“毒丸”对象确保消费者在关闭之前首先完成了队列中的所有工作。来个例子:
只有在生产者和消费者的数量都已知的条件下,才可以使用“毒丸”对象。上述的解决方案可以扩展到多个生产者:只需每个生产者都向队列中放入一个毒丸对象,并且消费者仅当在接收到Nproducers个毒丸对象时才停止。
-7.2.4 示例:只执行一次分服务
下面程序的checkMail方法能在多台主机上并行地检查新邮件。它创建一个私有的Executor,并向每台主机提交一个任务。然后,当所有邮件检查任务都执行完成后,关闭Executor并等待结束。
ExecuteService提供的shutdown方法:平滑的关闭ExecutorService,当此方法被调用时,ExecutorService停止接收新的任务并且等待已经提交的任务(包含提交正在执行和提交未执行)执行完成。当所有提交任务执行完毕,线程池即被关闭。
awaitTermination方法:接收timeout和TimeUnit两个参数,用于设定超时时间及单位。当等待超过设定时间时,会监测ExecutorService是否已经关闭,若关闭则返回true,否则返回false。一般情况下会和shutdown方法组合使用。
-7.2.5 shutdownNow的局限性
当通过shutdownNow来强行关闭ExecuteService时,它会尝试取消正在执行的任务,并返回所有已提交但尚未开始的任务,从而将这些任务写入日志或者保存起来以便之后进行处理。但是,我们无法通过常规方法来找出哪些任务已经开始但尚未结束。意思就是虽然shutdownNow会返回所有已提交但尚未开始的任务(都是Runnable), 但是却返回不了已经在执行但还未结束的任务,只能将这些任务取消掉。有的时候我们需要知道并保存这个状态,所以不仅要知道哪些任务还没有开始,而且还要知道哪些正在执行的任务还没有完成。
下面的程序给出了如何在关闭过程中判断正在执行的任务。
在程序清单7-22的WebCrawler中给出了TrackingExecutor的用法。网页爬虫程序的工作通常是无穷无尽的,因此当爬虫程序关闭时,我们通常希望保存它的状态(这里的意思就是要保存程序正在处理的网页),以便稍后重新启功时继续处理这些网页。
在TrackingExecutor中存在一个不可避免的竞态条件,从而产生误报问题:一些认为已取消的任务实际上已经执行完成。原因是在任务执行最后一条指令以及线程池将任务记录为“结束”的两个时刻之间,线程池可能被关闭。如果任务是幂等的(即将任务执行两次的结果与执行一次会得到相同的结果),那么这不会存在问题,否则需考虑这种风险。
7.3 处理非正常的线程中止
首先应该明确的是导致线程提前死亡的最主要原因是RuntimeException,如何处理这种非正常的线程中止来确保多线程情况下的线程安全性呢?简而言之就是利用捕获异常处理器和故障通知机制。下面的程序给出了如何在线程池内部构建一个工作者线程:
上面的是一种主动的方法来解决为检查异常。在Thread API中提供了UncaughtExceptionHandler,它能检测出由于未捕获的异常而终结的情况。当一个线程由于未捕获异常而退出时,JVM会把这个事件报告给UncaughtExceptionHandler异常处理器。
最常见的响应方式是将一个错误信息以及相应的栈追踪信息写入应用程序中:
要为线程池中的所有线程设置一个UncaughtExceptionHandler,需要为ThreadPoolExecutor的构造函数提供一个ThreadFactory。
7.4 JVM关闭
-7.4.1 关闭钩子
正常的JVM关闭中,JVM首先调用所有已注册的关闭钩子。这个关闭钩子指的是通过Runtime.addShutdownHook注册但尚未开始的线程。后面看不明白了,pass....
-7.4.2 守护线程
线程可以分为两种:普通线程和守护线程。在JVM启动时创建的所有线程中,除了主线程以外,其他的线程都是守护线程(如垃圾回收器以及其他执行辅助工作的线程)。当创建一个新线程时,新线程将继承创建它的线程的守护状态,因此在默认情况下,主线程创建的所有线程都是普通线程。当一个线程退出时,JVM会检查其他正在运行的线程,如果这些线程都是守护线程,那么JVM会正常退出。当JVM停止时,所有仍然存在的守护线程都将被抛弃,既不会执行finally代码块,也不会执行回卷栈。
应该尽可能少使用守护线程——很少有操作能够在不进行清理的情况下被安全地抛弃。特别是,如果在守护线程中执行可能包含I/O操作的任务将会是一种危险的行为。
-7.4.3 终结器
有些资源如文件句柄或套接字,当不需要时必须现实地交还给操作系统,而不是通过垃圾回收器回收。为了实现这个功能,垃圾回收器对哪些定义了finalize方法的对象会经行特殊处理:在回收器释放它们后。调用它们的finalize方法,从而保证一些持久化的资源被释放。大多数情况下,通过使用finally代码块和显示的close方法,能够比终结器更好的管理资源,所以要避免使用终结器。
《java并发编程实战》读书笔记6--取消与关闭的更多相关文章
- Java并发编程实战 读书笔记(一)
最近在看多线程经典书籍Java并发变成实战,很多概念有疑惑,虽然工作中很少用到多线程,但觉得还是自己太弱了.加油.记一些随笔.下面简单介绍一下线程. 一 线程与进程 进程与线程的解释 个人觉 ...
- Java并发编程实战 读书笔记(二)
关于发布和逸出 并发编程实践中,this引用逃逸("this"escape)是指对象还没有构造完成,它的this引用就被发布出去了.这是危及到线程安全的,因为其他线程有可能通过这个 ...
- 《java并发编程实战》笔记
<java并发编程实战>这本书配合并发编程网中的并发系列文章一起看,效果会好很多. 并发系列的文章链接为: Java并发性和多线程介绍目录 建议: <java并发编程实战>第 ...
- Java多线程编程实战读书笔记(一)
多线程的基础概念本人在学习多线程的时候发现一本书——java多线程编程实战指南.整理了一下书中的概念制作成了思维导图的形式.按照书中的章节整理,并添加一些个人的理解.
- Java并发编程实践读书笔记(4)任务取消和关闭
任务的取消 中断传递原理 Java中没有抢占式中断,就是武力让线程直接中断. Java中的中断可以理解为就是一种简单的消息机制.某个线程可以向其他线程发送消息,告诉你“你应该中断了”.收到这条消息的线 ...
- 《Java并发编程实战》笔记-取消与关闭
1,中断是实现取消的最合理方式.2,对中断操作的正确理解是:它并不会真正地中断一个正在运行的线程,而只是发出中断请求,然后由线程在下一个合适的时刻中断自己.3,区分任务和线程对中断的反应是很重要的4, ...
- Java并发编程实践读书笔记(5) 线程池的使用
Executor与Task的耦合性 1,除非线程池很非常大,否则一个Task不要依赖同一个线程服务中的另外一个Task,因为这样容易造成死锁: 2,线程的执行是并行的,所以在设计Task的时候要考虑到 ...
- Java并发编程艺术读书笔记
1.多线程在CPU切换过程中,由于需要保存线程之前状态和加载新线程状态,成为上下文切换,上下文切换会造成消耗系统内存.所以,可合理控制线程数量. 如何控制: (1)使用ps -ef|grep appn ...
- Java并发编程实践(读书笔记) 任务执行(未完)
任务的定义 大多数并发程序都是围绕任务进行管理的.任务就是抽象和离散的工作单元. 任务的执行策略 1.顺序的执行任务 这种策略的特点是一般只有按顺序处理到来的任务.一次只能处理一个任务,后来其它任 ...
- Java并发编程实践读书笔记(2)多线程基础组件
同步容器 同步容器是指那些对所有的操作都进行加锁(synchronize)的容器.比如Vector.HashTable和Collections.synchronizedXXX返回系列对象: 可以看到, ...
随机推荐
- 多线程中Local Store Slot(本地存储槽)[转]
1. 使用ThreadStatic特性 ThreadStatic特性是最简单的TLS使用,且只支持静态字段,只需要在字段上标记这个特性就可以了: [ThreadStatic] static str ...
- Eclipse NDK 打印LOG信息(都在jni目录下操作)
http://blog.csdn.net/u013045971/article/details/46448975 1 在.c文件中,引用头文件,定义TAG.LOG宏: #include <and ...
- #define与typedef
#define(宏定义)只是简单的字符串代换(原地扩展),它本身并不在编译过程中进行,而是在这之前(预处理过程)就已经完成了. typedef是为了增加可读性而为标识符另起的新名称(仅仅只是个别名), ...
- each jquery
<div class="first"> <span>投保人数:</span> <input type="text" i ...
- Intellij IDEA 使用jrebel运行spring-boot并实现自动编译进行热部署
在使用jrebel运行spring-boot的时候,会发现一个很棘手的问题,就是项目不能自动编译,不能自动编译就不能实现热部署.(使用jar包方式运行的时候) 那么我们就要解决自动编译的问题,首先: ...
- Java集合(3)一 红黑树、TreeMap与TreeSet(上)
目录 Java集合(1)一 集合框架 Java集合(2)一 ArrayList 与 LinkList Java集合(3)一 红黑树.TreeMap与TreeSet(上) Java集合(4)一 红黑树. ...
- LightOJ 1028 - Trailing Zeroes (I) 质因数分解/排列组合
题意:10000组数据 问一个数n[1,1e12] 在k进制下有末尾0的k的个数. 思路:题意很明显,就是求n的因子个数,本来想直接预处理欧拉函数,然后拿它减n就行了.但注意是1e12次方法不可行.而 ...
- Packet Tracer 5.0 构建CCNA实验(2)—— 配置VLAN
Packet Tracer 5.0 构建CCNA实验(2)—— 配置VLAN Vlan(Virtual Local Area Network) 即虚拟局域网.VLAN可以把同一个物理网络划分为多个逻辑 ...
- 希尔排序Shell sort
希尔排序Shell Sort是基于插入排序的一种改进,同样分成两部分, 第一部分,希尔排序介绍 第二部分,如何选取关键字,选取关键字是希尔排序的关键 第一块希尔排序介绍 准备待排数组[6 2 4 1 ...
- 【BZOJ4880】排名的战争 [暴力]
排名的战争 Time Limit: 8 Sec Memory Limit: 256 MB[Submit][Status][Discuss] Description 小Q是一名出色的质检员,他负责质检 ...