<<java 并发编程>>第七章:取消和关闭
Java没有提供任何机制来安全地终止线程,虽然Thread.stop和suspend等方法提供了这样的机制,但是存在严重的缺陷,应该避免使用这些方法。但是Java提供了中断Interruption机制,这是一种协作机制,能够使一个线程终止另一个线程的当前工作。
这种协作方式是必要的,我们很少希望某个任务线程或者服务立即停止,因为这种立即停止会时某个共享的数据结构处于不一致的状态。相反,在编写任务和服务的时候可以使用一种协作方式:当需要停止的时候,它们会先清除当前正在执行的工作,然后再结束。
7.1 任务取消
如果外部代码能够在某个操作正常完成之前将其置于 完成 状态,那么这个操作就可以称为可取消的Cancellable
其中一种协作机制是设置一个取消标志Cancellation Requested标志,而任务定期查看该标志。
- @ThreadSafe
- public class PrimeGenerator implements Runnable {
- private static ExecutorService exec = Executors.newCachedThreadPool();
- @GuardedBy("this")
- private final List<BigInteger> primes = new ArrayList<BigInteger>();
- private volatile boolean cancelled;
- public void run() {
- BigInteger p = BigInteger.ONE;
- while (!cancelled) {
- p = p.nextProbablePrime();
- synchronized (this) {
- primes.add(p);
- }
- }
- }
- public void cancel() {
- cancelled = true;
- }
- public synchronized List<BigInteger> get() {
- return new ArrayList<BigInteger>(primes);
- }
- static List<BigInteger> aSecondOfPrimes() throws InterruptedException {
- PrimeGenerator generator = new PrimeGenerator();
- exec.execute(generator);
- try {
- SECONDS.sleep(1);
- } finally {
- generator.cancel();
- }
- return generator.get();
- }
- }
在Java的API或语言规范中,并没有将中断与任何语义关联起来,但实际上,如果在取消之外的其他操作中使用中断,那么都是不合适的,并且很难支撑起更大的应用。
每个线程都有一个boolean类型的中断状态。但中断线程时,这个线程的中断状态将被设置成true。
Thread中的三个方法:
public void interrupt() 中断一个线程
public boolean isInterrupted() 获取中断标志,判断是否中断
public static boolean interrupted() 清楚中断状态,并返回它之前的状态值
线程在阻塞状态下发生中断的时候会抛出InterruptedException,例如Thread.sleep(), Thread.wait(), Thread.join()等方法。
当线程在非阻塞状态下中断的时候,它的中断状态将被设置,然后根据检查中断状态来判断是否中断。
调用interrupt并不意味着立即停止目标线程正在进行的工作,而只是传递了请求中断的消息,换句话说,仅仅修改了线程的isInterrupted标志字段。
通常,中断时实现取消的最合理方式。
- public class PrimeProducer extends Thread {
- private final BlockingQueue<BigInteger> queue;
- PrimeProducer(BlockingQueue<BigInteger> queue) {
- this.queue = queue;
- }
- public void run() {
- try {
- BigInteger p = BigInteger.ONE;
- while (!Thread.currentThread().isInterrupted())
- queue.put(p = p.nextProbablePrime());
- } catch (InterruptedException consumed) {
- /* Allow thread to exit */
- }
- }
- public void cancel() {
- interrupt();
- }
- }
7.1.3 响应中断
只有实现了线程中断策略的代码才可以屏蔽中断请求,在常规任务和库代码中都不应该屏蔽中断请求。
两种方法响应中断:
- public class NoncancelableTask {
- public Task getNextTask(BlockingQueue<Task> queue) {
- boolean interrupted = false;
- try {
- while (true) {
- try {
- return queue.take();
- } catch (InterruptedException e) {
- interrupted = true;
- // fall through and retry
- }
- }
- } finally {
- if (interrupted)
- Thread.currentThread().interrupt();
- }
- }
- interface Task {
- }
- }
- public class TimedRun {
- private static final ExecutorService taskExec = Executors.newCachedThreadPool();
- public static void timedRun(Runnable r, long timeout, TimeUnit unit)
- throws InterruptedException {
- Future<?> task = taskExec.submit(r);
- try {
- task.get(timeout, unit);
- } catch (TimeoutException e) {
- // task will be cancelled below
- } catch (ExecutionException e) {
- // exception thrown in task; rethrow
- throw launderThrowable(e.getCause());
- } finally {
- // Harmless if task already completed
- task.cancel(true); // interrupt if running
- }
- }
- }
- public abstract class SocketUsingTask<T> implements CancellableTask<T> {
- @GuardedBy("this")
- private Socket socket;
- protected synchronized void setSocket(Socket s) {
- socket = s;
- }
- public synchronized void cancel() {
- try {
- if (socket != null)
- socket.close();
- } catch (IOException ignored) {
- }
- }
- public RunnableFuture<T> newTask() {
- return new FutureTask<T>(this) {
- public boolean cancel(boolean mayInterruptIfRunning) {
- try {
- SocketUsingTask.this.cancel();
- } finally {
- return super.cancel(mayInterruptIfRunning);
- }
- }
- };
- }
- }
- interface CancellableTask<T> extends Callable<T> {
- void cancel();
- RunnableFuture<T> newTask();
- }
7.2 停止基于线程的服务
应用程序通常会创建拥有多个线程的服务,如果应用程序准备退出,那么这些服务所拥有的线程也需要正确的结束,由于java没有抢占式方法停止线程,因此需要它们自行结束。
正确的封装原则是:除非拥有某个线程,否则不要对该线程进行操控,例如中断线程或者修改优先级等。
线程有个相应的所有者,即创建该线程的类,因此线程池是其工作者线程的所有者,如果要中断线程,那么应该使用线程池去中断。
线程的所有权是不可传递的。服务应该提供生命周期方法Lifecycle Method来关闭它自己以及它所拥有的线程。这样当应用程序关闭该服务的时候,服务就可以关闭所有的线程了。在ExecutorService中提供了shutdown和shutdownNow等方法,同样,在其他拥有线程的服务方法中也应该提供类似的关闭机制。
Tips:对于持有线程的服务,只要服务的存在时间大于创建线程的方法的存在时间,那么就应该提供生命周期方法。
7.2.1 示例:日志服务
我们通常会在应用程序中加入log信息,一般用的框架就是log4j。但是这种内联的日志功能会给一些高容量Highvolume应用程序带来一定的性能开销。另外一种替代方法是通过调用log方法将日志消息放入某个队列中,并由其他线程来处理。
- public class LogService {
- private final BlockingQueue<String> queue;
- private final LoggerThread loggerThread;
- private final PrintWriter writer;
- @GuardedBy("this")
- private boolean isShutdown;
- @GuardedBy("this")
- private int reservations;
- public LogService(Writer writer) {
- this.queue = new LinkedBlockingQueue<String>();
- this.loggerThread = new LoggerThread();
- this.writer = new PrintWriter(writer);
- }
- public void start() {
- loggerThread.start();
- }
- public void stop() {
- synchronized (this) {
- isShutdown = true;
- }
- loggerThread.interrupt();
- }
- public void log(String msg) throws InterruptedException {
- synchronized (this) {
- if (isShutdown)
- throw new IllegalStateException(/*...*/);
- ++reservations;
- }
- queue.put(msg);
- }
- private class LoggerThread extends Thread {
- public void run() {
- try {
- while (true) {
- try {
- synchronized (LogService.this) {
- if (isShutdown && reservations == 0)
- break;
- }
- String msg = queue.take();
- synchronized (LogService.this) {
- --reservations;
- }
- writer.println(msg);
- } catch (InterruptedException e) { /* retry */
- }
- }
- } finally {
- writer.close();
- }
- }
- }
- }
7.2.2 通过ExecutorService去关闭
简单的程序可以直接在main函数中启动和关闭全局的ExecutorService,而在复杂程序中,通常会将ExecutorService封装在某个更高级别的服务中,并且该服务提供自己的生命周期方法。下面我们利用ExecutorService重构上面的日志服务:
- public class LogService {
- public void stop() throws InterruptedException {
- try {
- exec.shutdown(); exec.awaitTermination(TIMEOUT, UNIT);
- }
- }
- }
7.2.3 利用Poison Pill对象关闭Producer-Consumer服务
7.2.5 当关闭线程池的时候,保存尚未开始的和开始后取消的任务数据,以备后面重新处理,下面是一个网页爬虫程序,关闭爬虫服务的时候将记录所有尚未开始的和已经取消的所有页面URL:
- public abstract class WebCrawler {
- private volatile TrackingExecutor exec;
- @GuardedBy("this")
- private final Set<URL> urlsToCrawl = new HashSet<URL>();
- private final ConcurrentMap<URL, Boolean> seen = new ConcurrentHashMap<URL, Boolean>();
- private static final long TIMEOUT = 500;
- private static final TimeUnit UNIT = MILLISECONDS;
- public WebCrawler(URL startUrl) {
- urlsToCrawl.add(startUrl);
- }
- public synchronized void start() {
- exec = new TrackingExecutor(Executors.newCachedThreadPool());
- for (URL url : urlsToCrawl) submitCrawlTask(url);
- urlsToCrawl.clear();
- }
- public synchronized void stop() throws InterruptedException {
- try {
- saveUncrawled(exec.shutdownNow());
- if (exec.awaitTermination(TIMEOUT, UNIT))
- saveUncrawled(exec.getCancelledTasks());
- } finally {
- exec = null;
- }
- }
- protected abstract List<URL> processPage(URL url);
- private void saveUncrawled(List<Runnable> uncrawled) {
- for (Runnable task : uncrawled)
- urlsToCrawl.add(((CrawlTask) task).getPage());
- }
- private void submitCrawlTask(URL u) {
- exec.execute(new CrawlTask(u));
- }
- private class CrawlTask implements Runnable {
- private final URL url;
- CrawlTask(URL url) {
- this.url = url;
- }
- private int count = 1;
- boolean alreadyCrawled() {
- return seen.putIfAbsent(url, true) != null;
- }
- void markUncrawled() {
- seen.remove(url);
- System.out.printf("marking %s uncrawled%n", url);
- }
- public void run() {
- for (URL link : processPage(url)) {
- if (Thread.currentThread().isInterrupted())
- return;
- submitCrawlTask(link);
- }
- }
- public URL getPage() {
- return url;
- }
- }
- }
- public class TrackingExecutor extends AbstractExecutorService {
- private final ExecutorService exec;
- private final Set<Runnable> tasksCancelledAtShutdown =
- Collections.synchronizedSet(new HashSet<Runnable>());
- public TrackingExecutor(ExecutorService exec) {
- this.exec = exec;
- }
- public void shutdown() {
- exec.shutdown();
- }
- public List<Runnable> shutdownNow() {
- return exec.shutdownNow();
- }
- public boolean isShutdown() {
- return exec.isShutdown();
- }
- public boolean isTerminated() {
- return exec.isTerminated();
- }
- public boolean awaitTermination(long timeout, TimeUnit unit)
- throws InterruptedException {
- return exec.awaitTermination(timeout, unit);
- }
- public List<Runnable> getCancelledTasks() {
- if (!exec.isTerminated())
- throw new IllegalStateException(/*...*/);
- return new ArrayList<Runnable>(tasksCancelledAtShutdown);
- }
- public void execute(final Runnable runnable) {
- exec.execute(new Runnable() {
- public void run() {
- try {
- runnable.run();
- } finally {
- if (isShutdown()
- && Thread.currentThread().isInterrupted())
- tasksCancelledAtShutdown.add(runnable);
- }
- }
- });
- }
- }
7.3 处理非正常的线程终止
通过给应用程序提供一个UncaughtExceptionHandler异常处理器来处理未捕获的异常:
- public class UEHLogger implements Thread.UncaughtExceptionHandler {
- public void uncaughtException(Thread t, Throwable e) {
- Logger logger = Logger.getAnonymousLogger();
- logger.log(Level.SEVERE, "Thread terminated with exception: " + t.getName(), e);
- }
- }
只有通过execute提交的任务,才能将它抛出的异常交给未捕获异常处理器。而通过submit提交的任务,无论是抛出未检查异常还是已检查异常,都将被认为是任务返回状态的一部分
7.4 JVM关闭的时候提供关闭钩子
在正常关闭JVM的时候,JVM首先调用所有已注册的关闭钩子Shutdown Hook。关闭钩子可以用来实现服务或者应用程序的清理工作,例如删除临时文件,或者清除无法由操作系统自动清除的资源。
最佳实践是对所有服务都使用同一个关闭钩子,并且在该关闭钩子中执行一系列的关闭操作。这确保了关闭操作在单个线程中串行执行,从而避免竞态条件的发生或者死锁问题。
- Runtime.getRuntime().addShutdownHook(new Thread() {
- public void run() {
- try{LogService.this.stop();} catch(InterruptedException) {..}
- }
- })
总结:在任务、线程、服务以及应用程序等模块中的生命周期结束问题,可能会增加它们在设计和实现的时候的复杂性。我们通过利用FutureTask和Executor框架,可以帮助我们构建可取消的任务和服务。
原文地址:http://yidao620c.iteye.com/blog/1856914
<<java 并发编程>>第七章:取消和关闭的更多相关文章
- 《Java并发编程实战》笔记-取消与关闭
1,中断是实现取消的最合理方式.2,对中断操作的正确理解是:它并不会真正地中断一个正在运行的线程,而只是发出中断请求,然后由线程在下一个合适的时刻中断自己.3,区分任务和线程对中断的反应是很重要的4, ...
- java并发编程系列七:volatile和sinchronized底层实现原理
一.线程安全 1. 怎样让多线程下的类安全起来 无状态.加锁.让类不可变.栈封闭.安全的发布对象 2. 死锁 2.1 死锁概念及解决死锁的原则 一定发生在多个线程争夺多个资源里的情况下,发生的原因是 ...
- Java并发编程(七)ConcurrentLinkedQueue的实现原理和源码分析
相关文章 Java并发编程(一)线程定义.状态和属性 Java并发编程(二)同步 Java并发编程(三)volatile域 Java并发编程(四)Java内存模型 Java并发编程(五)Concurr ...
- 《Java并发编程实战》第七章 取消与关闭 读书笔记
Java没有提供不论什么机制来安全地(抢占式方法)终止线程,尽管Thread.stop和suspend等方法提供了这种机制,可是因为存在着一些严重的缺陷,因此应该避免使用. 但它提供了中断In ...
- java并发编程(七)synchronized详解
Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 一.当两个并发线程访问同一个对象object中的这个synchronized( ...
- java并发编程实战:第七章----取消与关闭
Java没有提供任何机制来安全地终止线程(虽然Thread.stop和suspend方法提供了这样的机制,但由于存在缺陷,因此应该避免使用 中断:一种协作机制,能够使一个线程终止另一个线程的当前工作 ...
- Java并发编程实战4章
第4章主要介绍如何构造线程安全类. 在设计线程安全类的过程中,需要包含以下三个基本要素: 找出构成对象状态的所有变量. 找出约束状态变量的不变性条件. 建立对象状态的并发访问管理策略. 构造线程安全类 ...
- JAVA / MySql 编程——第七章 JDBC
1.JDBC:JDBA是Java数据库连接(Java DataBase Connectivity)技术的简称,提供连接各种常用数据库的能力: ●Java是通过JDBC技术实现对各种数据 ...
- java面向对象编程— —第七章 继承
7.1继承的起源 继承(Inheritance),即在面向对象编程中,可以通过扩展(extends)一个已有的类,并继承该类的属性的行为,来创建一个新的类. 已有的类称为父类(也可以称为基类,超类), ...
随机推荐
- 与 空格的区别
nbsp 是 Non-Breaking SPace的缩写,即“不被折断的空格”,当两个单词使用 连接时,这两个单词就不会被分隔为2行,如下面 <div id="div1" ...
- 翻译-让ng的$http服务与jQuerr.ajax()一样易用
Make AngularJS $http service behave like jQuery.ajax() 让ng的$http服务与jQuerr.ajax()一样易用 作者zeke There is ...
- TortoiseSVN显示图标不正常
Windows Explorer Shell支持的Overlay Icon最多15个,除去系统使用,只有11个.如果其他程序占用了,那么乌龟SVN就无法显示了.注册表定位到:HKEY_LOCAL_MA ...
- objective-c基础教程
command+shift+c foundation框架包含的头文件 /system/library/frameworkks/foundation.framework/headers/
- 大神眼中的React Native--备用
当我第一次尝试ReactNative的时候,我觉得这只是网页开发者涉足原生移动应用领域的歪门邪道. 我认为一个js开发者可以使用javascript来构建iPhone应用确实是一件很酷的事情,但是我很 ...
- Java中传参的值传递和引用传递问题(转)
今天遇到了一个java程序,需要用参数来返回值(虽然最后用另一种方法实现了),在网上看到这样一篇文章,很受启发. 本文章来自于http://hi.baidu.com/xzhilie/blog/item ...
- STM32启动文件的选择
移植了同事一个程序,然后死活不能用,发现启动文件错了,明天继续调.真把人折腾死了. stm32给的库文件太琐碎了,正如它的芯片型号一样繁多,例如启动文件: 网上查到的各个文件的解释是: startup ...
- Unity3d 项目管理
Unity3d 项目管理 1.项目管理采用TortoiseSVN方式进行管理,但是也要结合人员管理的方式,尽量在U3D中多采用Scene(关卡的方式),以一下目录的方式进行管理! 以名字的方式进行合 ...
- Struts2 实现分页
1.转自:http://www.cnblogs.com/shiyangxt/archive/2008/11/04/1316737.html环境:MyEclipse6.5+Mysql5+struts2. ...
- JFS与JFS2的区别
请问一下JFS与JFS2的区别? 支持最大的文件? 普通JFS:2G:支持大文件JFS:64G:JFS2:1T 支持最大的文件系统?普通JFS,支持大文件JFS,JFS2分别是多大呢? The max ...